blob: 3d4922f5e2fc0e316f23bfb84b05e8a585f18962 [file] [log] [blame] [raw]
package li.cil.oc.common
import java.util.Calendar
import cpw.mods.fml.common.Optional
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.common.gameevent.PlayerEvent._
import cpw.mods.fml.common.gameevent.TickEvent
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent
import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent
import cpw.mods.fml.common.network.FMLNetworkEvent.ClientConnectedToServerEvent
import li.cil.oc._
import li.cil.oc.api.Network
import li.cil.oc.api.detail.ItemInfo
import li.cil.oc.api.internal.Rack
import li.cil.oc.api.internal.Server
import li.cil.oc.api.machine.MachineHost
import li.cil.oc.client.renderer.PetRenderer
import li.cil.oc.common.asm.ClassTransformer
import li.cil.oc.common.component.TerminalServer
import li.cil.oc.common.item.data.MicrocontrollerData
import li.cil.oc.common.item.data.RobotData
import li.cil.oc.common.item.data.TabletData
import li.cil.oc.common.recipe.Recipes
import li.cil.oc.common.tileentity.Robot
import li.cil.oc.common.tileentity.traits.power
import li.cil.oc.integration.Mods
import li.cil.oc.integration.util
import li.cil.oc.integration.util.Wrench
import li.cil.oc.server.component.Keyboard
import li.cil.oc.server.machine.Callbacks
import li.cil.oc.server.machine.Machine
import li.cil.oc.server.machine.luac.LuaStateFactory
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util.ExtendedWorld._
import li.cil.oc.util._
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.item.ItemStack
import net.minecraft.server.MinecraftServer
import net.minecraft.tileentity.TileEntity
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.util.FakePlayer
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.event.entity.EntityJoinWorldEvent
import net.minecraftforge.event.world.BlockEvent
import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object EventHandler {
private val pendingServer = mutable.Buffer.empty[() => Unit]
private val pendingClient = mutable.Buffer.empty[() => Unit]
private val runningRobots = mutable.Set.empty[Robot]
private val keyboards = java.util.Collections.newSetFromMap[Keyboard](new java.util.WeakHashMap[Keyboard, java.lang.Boolean])
private val machines = mutable.Set.empty[Machine]
def onRobotStart(robot: Robot): Unit = runningRobots += robot
def onRobotStopped(robot: Robot): Unit = runningRobots -= robot
def addKeyboard(keyboard: Keyboard): Unit = keyboards += keyboard
def scheduleClose(machine: Machine): Unit = machines += machine
def unscheduleClose(machine: Machine): Unit = machines -= machine
def scheduleServer(tileEntity: TileEntity) {
if (SideTracker.isServer) pendingServer.synchronized {
pendingServer += (() => Network.joinOrCreateNetwork(tileEntity))
}
}
def scheduleServer(f: () => Unit) {
pendingServer.synchronized {
pendingServer += f
}
}
def scheduleClient(f: () => Unit) {
pendingClient.synchronized {
pendingClient += f
}
}
@Optional.Method(modid = Mods.IDs.ForgeMultipart)
def scheduleFMP(tileEntity: () => TileEntity) {
if (SideTracker.isServer) pendingServer.synchronized {
pendingServer += (() => Network.joinOrCreateNetwork(tileEntity()))
}
}
@Optional.Method(modid = Mods.IDs.AppliedEnergistics2)
def scheduleAE2Add(tileEntity: power.AppliedEnergistics2) {
if (SideTracker.isServer) pendingServer.synchronized {
pendingServer += (() => if (!tileEntity.isInvalid) {
tileEntity.getGridNode(ForgeDirection.UNKNOWN).updateState()
})
}
}
@Optional.Method(modid = Mods.IDs.IndustrialCraft2)
def scheduleIC2Add(tileEntity: power.IndustrialCraft2Experimental) {
if (SideTracker.isServer) pendingServer.synchronized {
pendingServer += (() => if (!tileEntity.addedToIC2PowerGrid && !tileEntity.isInvalid) {
MinecraftForge.EVENT_BUS.post(new ic2.api.energy.event.EnergyTileLoadEvent(tileEntity.asInstanceOf[ic2.api.energy.tile.IEnergyTile]))
tileEntity.addedToIC2PowerGrid = true
})
}
}
@Optional.Method(modid = Mods.IDs.IndustrialCraft2Classic)
def scheduleIC2Add(tileEntity: power.IndustrialCraft2Classic) {
if (SideTracker.isServer) pendingServer.synchronized {
pendingServer += (() => if (!tileEntity.addedToIC2PowerGrid && !tileEntity.isInvalid) {
MinecraftForge.EVENT_BUS.post(new ic2classic.api.energy.event.EnergyTileLoadEvent(tileEntity.asInstanceOf[ic2classic.api.energy.tile.IEnergyTile]))
tileEntity.addedToIC2PowerGrid = true
})
}
}
def scheduleWirelessRedstone(rs: server.component.RedstoneWireless) {
if (SideTracker.isServer) pendingServer.synchronized {
pendingServer += (() => if (rs.node.network != null) {
util.WirelessRedstone.addReceiver(rs)
util.WirelessRedstone.updateOutput(rs)
})
}
}
@SubscribeEvent
def onServerTick(e: ServerTickEvent) = if (e.phase == TickEvent.Phase.START) {
pendingServer.synchronized {
val adds = pendingServer.toArray
pendingServer.clear()
adds
} foreach (callback => {
try callback() catch {
case t: Throwable => OpenComputers.log.warn("Error in scheduled tick action.", t)
}
})
val invalid = mutable.ArrayBuffer.empty[Robot]
runningRobots.foreach(robot => {
if (robot.isInvalid) invalid += robot
else if (robot.world != null) robot.machine.update()
})
runningRobots --= invalid
}
else if (e.phase == TickEvent.Phase.END) {
// Clean up machines *after* a tick, to allow stuff to be saved, first.
val closed = mutable.ArrayBuffer.empty[Machine]
machines.foreach(machine => if (machine.tryClose()) {
closed += machine
if (machine.host.world == null || !machine.host.world.blockExists(BlockPosition(machine.host))) {
if (machine.node != null) machine.node.remove()
}
})
machines --= closed
}
@SubscribeEvent
def onClientTick(e: ClientTickEvent) = if (e.phase == TickEvent.Phase.START) {
pendingClient.synchronized {
val adds = pendingClient.toArray
pendingClient.clear()
adds
} foreach (callback => {
try callback() catch {
case t: Throwable => OpenComputers.log.warn("Error in scheduled tick action.", t)
}
})
}
@SubscribeEvent
def playerLoggedIn(e: PlayerLoggedInEvent) {
if (SideTracker.isServer) e.player match {
case _: FakePlayer => // Nope
case player: EntityPlayerMP =>
if (!LuaStateFactory.isAvailable) {
player.addChatMessage(Localization.Chat.WarningLuaFallback)
}
if (!Settings.get.pureIgnorePower && Settings.get.ignorePower) {
player.addChatMessage(Localization.Chat.WarningPower)
}
if (Recipes.hadErrors) {
player.addChatMessage(Localization.Chat.WarningRecipes)
}
if (ClassTransformer.hadErrors) {
player.addChatMessage(Localization.Chat.WarningClassTransformer)
}
if (ClassTransformer.hadSimpleComponentErrors) {
player.addChatMessage(Localization.Chat.WarningSimpleComponent)
}
ServerPacketSender.sendPetVisibility(None, Some(player))
ServerPacketSender.sendLootDisks(player)
// Do update check in local games and for OPs.
if (!Mods.VersionChecker.isAvailable && (!MinecraftServer.getServer.isDedicatedServer || MinecraftServer.getServer.getConfigurationManager.func_152596_g(player.getGameProfile))) {
Future {
UpdateCheck.info onSuccess {
case Some(release) => player.addChatMessage(Localization.Chat.InfoNewVersion(release.tag_name))
}
}
}
case _ =>
}
}
@SubscribeEvent
def clientLoggedIn(e: ClientConnectedToServerEvent) {
PetRenderer.isInitialized = false
PetRenderer.hidden.clear()
Loot.disksForClient.clear()
client.Sound.startLoop(null, "computer_running", 0f, 0)
scheduleServer(() => client.Sound.stopLoop(null))
}
@SubscribeEvent
def onBlockBreak(e: BlockEvent.BreakEvent): Unit = {
e.world.getTileEntity(e.x, e.y, e.z) match {
case c: tileentity.Case =>
if (c.isCreative && (!e.getPlayer.capabilities.isCreativeMode || !c.canInteract(e.getPlayer.getCommandSenderName))) {
e.setCanceled(true)
}
case r: tileentity.RobotProxy =>
val robot = r.robot
if (robot.isCreative && (!e.getPlayer.capabilities.isCreativeMode || !robot.canInteract(e.getPlayer.getCommandSenderName))) {
e.setCanceled(true)
}
case _ =>
}
}
@SubscribeEvent
def onPlayerRespawn(e: PlayerRespawnEvent) {
keyboards.foreach(_.releasePressedKeys(e.player))
}
@SubscribeEvent
def onPlayerChangedDimension(e: PlayerChangedDimensionEvent) {
keyboards.foreach(_.releasePressedKeys(e.player))
}
@SubscribeEvent
def onPlayerLogout(e: PlayerLoggedOutEvent) {
keyboards.foreach(_.releasePressedKeys(e.player))
}
@SubscribeEvent
def onEntityJoinWorld(e: EntityJoinWorldEvent): Unit = {
if (Settings.get.giveManualToNewPlayers && !e.world.isRemote) e.entity match {
case player: EntityPlayer if !player.isInstanceOf[FakePlayer] =>
val persistedData = PlayerUtils.persistedData(player)
if (!persistedData.getBoolean(Settings.namespace + "receivedManual")) {
persistedData.setBoolean(Settings.namespace + "receivedManual", true)
player.inventory.addItemStackToInventory(api.Items.get(Constants.ItemName.Manual).createItemStack(1))
}
case _ =>
}
}
lazy val drone = api.Items.get(Constants.ItemName.Drone)
lazy val eeprom = api.Items.get(Constants.ItemName.EEPROM)
lazy val floppy = api.Items.get(Constants.ItemName.Floppy)
lazy val mcu = api.Items.get(Constants.BlockName.Microcontroller)
lazy val navigationUpgrade = api.Items.get(Constants.ItemName.NavigationUpgrade)
lazy val robot = api.Items.get(Constants.BlockName.Robot)
lazy val tablet = api.Items.get(Constants.ItemName.Tablet)
@SubscribeEvent
def onCrafting(e: ItemCraftedEvent) = {
var didRecraft = false
didRecraft = recraft(e, navigationUpgrade, stack => {
// Restore the map currently used in the upgrade.
Option(api.Driver.driverFor(e.crafting)) match {
case Some(driver) => Option(ItemStack.loadItemStackFromNBT(driver.dataTag(stack).getCompoundTag(Settings.namespace + "map")))
case _ => None
}
}) || didRecraft
didRecraft = recraft(e, mcu, stack => {
// Restore EEPROM currently used in microcontroller.
new MicrocontrollerData(stack).components.find(api.Items.get(_) == eeprom)
}) || didRecraft
didRecraft = recraft(e, drone, stack => {
// Restore EEPROM currently used in drone.
new MicrocontrollerData(stack).components.find(api.Items.get(_) == eeprom)
}) || didRecraft
didRecraft = recraft(e, robot, stack => {
// Restore EEPROM currently used in robot.
new RobotData(stack).components.find(api.Items.get(_) == eeprom)
}) || didRecraft
didRecraft = recraft(e, tablet, stack => {
// Restore EEPROM currently used in tablet.
new TabletData(stack).items.collect { case Some(item) => item }.find(api.Items.get(_) == eeprom)
}) || didRecraft
didRecraft = {
if (Loot.isLootDisk(e.crafting)) {
val stacks = (0 until e.craftMatrix.getSizeInventory).flatMap(i => Option(e.craftMatrix.getStackInSlot(i))).toArray
if (stacks.length == 2) stacks.find(Wrench.isWrench) match {
case Some(stack) =>
stack.stackSize += 1
true
case _ => didRecraft
}
else didRecraft
}
else didRecraft
}
// Presents?
e.player match {
case _: FakePlayer => // No presents for you, automaton. Such discrimination. Much bad conscience.
case player: EntityPlayerMP if player.getEntityWorld != null && !player.getEntityWorld.isRemote =>
// Presents!? If we didn't recraft, it's an OC item, and the time is right...
if (Settings.get.presentChance > 0 && !didRecraft && api.Items.get(e.crafting) != null &&
e.player.getRNG.nextFloat() < Settings.get.presentChance && timeForPresents) {
// Presents!
val present = api.Items.get(Constants.ItemName.Present).createItemStack(1)
e.player.worldObj.playSoundAtEntity(e.player, "note.pling", 0.2f, 1f)
InventoryUtils.addToPlayerInventory(present, e.player)
}
case _ => // Nope.
}
Achievement.onCraft(e.crafting, e.player)
}
@SubscribeEvent
def onPickup(e: ItemPickupEvent): Unit = {
val entity = e.pickedUp
Option(entity).flatMap(e => Option(e.getEntityItem)) match {
case Some(stack) =>
Achievement.onAssemble(stack, e.player)
Achievement.onCraft(stack, e.player)
case _ => // Huh.
}
}
private def timeForPresents = {
val now = Calendar.getInstance()
val month = now.get(Calendar.MONTH)
val dayOfMonth = now.get(Calendar.DAY_OF_MONTH)
// On the 12th day of Christmas, my robot brought to me~
(month == Calendar.DECEMBER && dayOfMonth > 24) || (month == Calendar.JANUARY && dayOfMonth < 7) ||
(month == Calendar.FEBRUARY && dayOfMonth == 14) ||
(month == Calendar.APRIL && dayOfMonth == 22) ||
(month == Calendar.MAY && dayOfMonth == 1) ||
(month == Calendar.OCTOBER && dayOfMonth == 3) ||
(month == Calendar.DECEMBER && dayOfMonth == 14)
}
def isItTime = {
val now = Calendar.getInstance()
val month = now.get(Calendar.MONTH)
val dayOfMonth = now.get(Calendar.DAY_OF_MONTH)
month == Calendar.APRIL && dayOfMonth == 1
}
private def recraft(e: ItemCraftedEvent, item: ItemInfo, callback: ItemStack => Option[ItemStack]): Boolean = {
if (api.Items.get(e.crafting) == item) {
for (slot <- 0 until e.craftMatrix.getSizeInventory) {
val stack = e.craftMatrix.getStackInSlot(slot)
if (api.Items.get(stack) == item) {
callback(stack).foreach(extra =>
InventoryUtils.addToPlayerInventory(extra, e.player))
}
}
true
}
else false
}
// This is called from the ServerThread *and* the ClientShutdownThread, which
// can potentially happen at the same time... for whatever reason. So let's
// synchronize what we're doing here to avoid race conditions (e.g. when
// disposing networks, where this actually triggered an assert).
@SubscribeEvent
def onWorldUnload(e: WorldEvent.Unload): Unit = this.synchronized {
if (!e.world.isRemote) {
e.world.loadedTileEntityList.collect {
case te: tileentity.traits.TileEntity => te.dispose()
}
e.world.loadedEntityList.collect {
case host: MachineHost => host.machine.stop()
}
Callbacks.clear()
}
else {
TerminalServer.loaded.clear()
}
}
@SubscribeEvent
def onChunkUnload(e: ChunkEvent.Unload): Unit = {
if (!e.world.isRemote) {
e.getChunk.entityLists.foreach(_.collect {
case host: MachineHost => host.machine match {
case machine: Machine => scheduleClose(machine)
case _ => // Dafuq?
}
case rack: Rack =>
(0 until rack.getSizeInventory).
map(rack.getMountable).
collect { case server: Server if server.machine != null => server.machine.stop() }
})
}
}
}