diff --git a/Server/src/main/java/core/game/content/dialogue/DialogueInterpreter.java b/Server/src/main/java/core/game/content/dialogue/DialogueInterpreter.java index 80556cc23..577ab2d8c 100644 --- a/Server/src/main/java/core/game/content/dialogue/DialogueInterpreter.java +++ b/Server/src/main/java/core/game/content/dialogue/DialogueInterpreter.java @@ -204,6 +204,7 @@ public final class DialogueInterpreter { public boolean close() { if (dialogue != null || dialogueStage != null) { actions.clear(); + if (player.getInterfaceManager().getChatbox() != null && player.getInterfaceManager().getChatbox().getCloseEvent() != null) { return true; } @@ -211,8 +212,10 @@ public final class DialogueInterpreter { dialogueStage = null; player.getInterfaceManager().closeChatbox(); } - if (dialogue != null && dialogue.close()) { + if (dialogue != null) { + DialoguePlugin d = dialogue; dialogue = null; + d.close(); } } return dialogue == null && dialogueStage == null; diff --git a/Server/src/main/java/core/game/content/dialogue/DialoguePlugin.java b/Server/src/main/java/core/game/content/dialogue/DialoguePlugin.java index 962164acf..b814afc94 100644 --- a/Server/src/main/java/core/game/content/dialogue/DialoguePlugin.java +++ b/Server/src/main/java/core/game/content/dialogue/DialoguePlugin.java @@ -122,6 +122,7 @@ public abstract class DialoguePlugin implements Plugin { public boolean close() { player.getInterfaceManager().closeChatbox(); player.getInterfaceManager().openChatbox(137); + if(file != null) file.end(); finished = true; return true; } diff --git a/Server/src/main/java/core/game/system/task/TaskExecutor.java b/Server/src/main/java/core/game/system/task/TaskExecutor.java deleted file mode 100644 index ed9b7d73f..000000000 --- a/Server/src/main/java/core/game/system/task/TaskExecutor.java +++ /dev/null @@ -1,54 +0,0 @@ -package core.game.system.task; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; - -/** - * A class holding methods to execute tasks. - * @author Emperor - */ -public final class TaskExecutor { - - /** - * The executor to use. - */ - private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); - - /** - * The SQL task executor. - */ - private static final ScheduledExecutorService SQL_EXECUTOR = Executors.newSingleThreadScheduledExecutor(); - - /** - * Constructs a new {@code TaskExecutor} {@code Object}. - */ - private TaskExecutor() { - /* - * empty. - */ - } - - /** - * Executes an SQL handling task. - * @param task The task. - */ - public static void executeSQL(Runnable task) { - SQL_EXECUTOR.execute(task); - } - - /** - * Executes the task. - * @param task The task to execute. - */ - public static void execute(Runnable task) { - EXECUTOR.execute(task); - } - - /** - * Gets the executor. - * @return The executor. - */ - public static ScheduledExecutorService getExecutor() { - return EXECUTOR; - } -} \ No newline at end of file diff --git a/Server/src/main/java/core/game/system/task/TaskExecutor.kt b/Server/src/main/java/core/game/system/task/TaskExecutor.kt new file mode 100644 index 000000000..92f25fca0 --- /dev/null +++ b/Server/src/main/java/core/game/system/task/TaskExecutor.kt @@ -0,0 +1,33 @@ +package core.game.system.task + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +/** + * A class holding methods to execute tasks. + * @author Emperor + */ +object TaskExecutor { + + /** + * Executes an SQL handling task. + * @param task The task. + */ + @JvmStatic + fun executeSQL(task: () -> Unit) { + GlobalScope.launch { + task.invoke() + } + } + + /** + * Executes the task. + * @param task The task to execute. + */ + @JvmStatic + fun execute(task: () -> Unit) { + GlobalScope.launch { + task.invoke() + } + } +} \ No newline at end of file diff --git a/Server/src/main/java/core/gui/tab/UtilityTab.java b/Server/src/main/java/core/gui/tab/UtilityTab.java index d049431c5..d2c07a0b8 100644 --- a/Server/src/main/java/core/gui/tab/UtilityTab.java +++ b/Server/src/main/java/core/gui/tab/UtilityTab.java @@ -10,6 +10,7 @@ import core.gui.ConsoleFrame; import core.gui.ConsoleTab; import core.tools.PlayerLoader; import core.tools.StringUtils; +import kotlin.Unit; import javax.swing.*; import javax.swing.border.EtchedBorder; @@ -417,6 +418,7 @@ public final class UtilityTab extends ConsoleTab { populating = true; players.clear(); TaskExecutor.execute(() -> { + Thread.currentThread().setName("Utility Tab"); ConsoleFrame.getInstance().getPlayerTab().getPlayerNames().clear(); ConsoleFrame.getInstance().getPlayerTab().populatePlayerSearch(); for (String name : ConsoleFrame.getInstance().getPlayerTab().getPlayerNames()) { @@ -435,6 +437,7 @@ public final class UtilityTab extends ConsoleTab { populating = false; System.gc(); lblPlayersLoaded.setText("Players loaded: " + players.size()); + return Unit.INSTANCE; }); } diff --git a/Server/src/main/java/core/net/amsc/WorldCommunicator.java b/Server/src/main/java/core/net/amsc/WorldCommunicator.java index 39e9dc193..cce4f911e 100644 --- a/Server/src/main/java/core/net/amsc/WorldCommunicator.java +++ b/Server/src/main/java/core/net/amsc/WorldCommunicator.java @@ -7,6 +7,7 @@ import core.net.EventProducer; import core.net.IoSession; import core.net.NioReactor; import core.net.producer.MSHSEventProducer; +import kotlin.Unit; import rs09.game.node.entity.player.info.login.LoginParser; import rs09.game.system.SystemLogger; import rs09.game.world.GameWorld; @@ -76,15 +77,13 @@ public final class WorldCommunicator { return; } loginAttempts.put(parser.getDetails().getUsername(), parser); - TaskExecutor.executeSQL(new Runnable() { - @Override - public void run() { - if (!parser.getDetails().parse()) { - parser.getDetails().getSession().write(Response.INVALID_LOGIN_SERVER, true); - return; - } - MSPacketRepository.sendPlayerRegistry(parser); + TaskExecutor.executeSQL(() -> { + if (!parser.getDetails().parse()) { + parser.getDetails().getSession().write(Response.INVALID_LOGIN_SERVER, true); + return Unit.INSTANCE; } + MSPacketRepository.sendPlayerRegistry(parser); + return Unit.INSTANCE; }); } diff --git a/Server/src/main/java/core/net/registry/AccountRegister.java b/Server/src/main/java/core/net/registry/AccountRegister.java index 38bfd74d3..7fc98a9a5 100644 --- a/Server/src/main/java/core/net/registry/AccountRegister.java +++ b/Server/src/main/java/core/net/registry/AccountRegister.java @@ -8,6 +8,7 @@ import core.game.system.mysql.SQLManager; import core.game.system.task.TaskExecutor; import core.net.Constants; import core.net.IoSession; +import kotlin.Unit; import rs09.ServerConstants; import rs09.game.system.SystemLogger; import rs09.game.world.GameWorld; @@ -76,19 +77,17 @@ public class AccountRegister extends SQLEntryHandler { break; } System.out.println(username); - TaskExecutor.executeSQL(new Runnable() { - @Override - public void run() { - try { - if (PlayerSQLManager.hasSqlAccount(username, "username")) { - response(session, RegistryResponse.NOT_AVAILBLE_USER); - return; - } - response(session, RegistryResponse.SUCCESS); - } catch (SQLException e) { - e.printStackTrace(); + TaskExecutor.executeSQL(() -> { + try { + if (PlayerSQLManager.hasSqlAccount(username, "username")) { + response(session, RegistryResponse.NOT_AVAILBLE_USER); + return Unit.INSTANCE; } + response(session, RegistryResponse.SUCCESS); + } catch (SQLException e) { + e.printStackTrace(); } + return Unit.INSTANCE; }); break; case 36://Register details @@ -129,21 +128,19 @@ public class AccountRegister extends SQLEntryHandler { buffer.getInt(); @SuppressWarnings("deprecation") final RegistryDetails details = new RegistryDetails(name, SystemManager.getEncryption().hashPassword(password), new Date(year, month, day), country); - TaskExecutor.execute(new Runnable() { - @Override - public void run() { - try { - if (PlayerSQLManager.hasSqlAccount(name, "username")) { - response(session, RegistryResponse.CANNOT_CREATE); - return; - } - SQLEntryHandler.write(new AccountRegister(details)); - response(session, RegistryResponse.SUCCESS); - } catch (SQLException e) { - e.printStackTrace(); + TaskExecutor.execute(() -> { + try { + if (PlayerSQLManager.hasSqlAccount(name, "username")) { response(session, RegistryResponse.CANNOT_CREATE); + return Unit.INSTANCE; } + SQLEntryHandler.write(new AccountRegister(details)); + response(session, RegistryResponse.SUCCESS); + } catch (SQLException e) { + e.printStackTrace(); + response(session, RegistryResponse.CANNOT_CREATE); } + return Unit.INSTANCE; }); break; default: diff --git a/Server/src/main/kotlin/api/ContentAPI.kt b/Server/src/main/kotlin/api/ContentAPI.kt index 1bbbb2b78..a7b5d5c07 100644 --- a/Server/src/main/kotlin/api/ContentAPI.kt +++ b/Server/src/main/kotlin/api/ContentAPI.kt @@ -167,6 +167,26 @@ object ContentAPI { if(!player.inventory.add(item)) GroundItemManager.create(item,player) } + /** + * Clears an NPC with the "poof" smoke graphics commonly seen with random event NPCs. + * @param npc the NPC object to initialize + */ + @JvmStatic + fun poofClear(npc: NPC){ + submitWorldPulse(object : Pulse(){ + var counter = 0 + override fun pulse(): Boolean { + when(counter++){ + 2 -> { + npc.isInvisible = true; Graphics.send(Graphics(86), npc.location) + } + 3 -> npc.clear().also { return true } + } + return false + } + }) + } + /** * Check if an item exists in a player's bank * @param player the player whose bank to check @@ -545,9 +565,10 @@ object ContentAPI { */ @JvmStatic fun openDialogue(player: Player, dialogue: Any, vararg args: Any) { + player.dialogueInterpreter.close() when (dialogue) { - is Int -> player.dialogueInterpreter.open(dialogue, args) - is DialogueFile -> player.dialogueInterpreter.open(dialogue, args) + is Int -> player.dialogueInterpreter.open(dialogue, *args) + is DialogueFile -> player.dialogueInterpreter.open(dialogue, *args) else -> SystemLogger.logErr("Invalid object type passed to openDialogue() -> ${dialogue.javaClass.simpleName}") } } diff --git a/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt b/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt index b0fe0f876..83672a5e2 100644 --- a/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt +++ b/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt @@ -340,7 +340,7 @@ class ScriptAPI(private val bot: Player) { */ fun walkTo(loc: Location){ if(!bot.walkingQueue.isMoving) { - Executors.newSingleThreadExecutor().execute { + GlobalScope.launch { walkToIterator(loc) } } @@ -645,7 +645,7 @@ class ScriptAPI(private val bot: Player) { * @return true if item was successfully bought, false if not. */ fun buyFromGE(bot: Player, itemID: Int, amount: Int){ - Executors.newSingleThreadExecutor().execute{ + GlobalScope.launch { val offer = GrandExchangeOffer() offer.itemID = itemID offer.sell = false diff --git a/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatState.kt b/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatState.kt index 0adf3c315..a56fc69f0 100644 --- a/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatState.kt +++ b/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatState.kt @@ -7,6 +7,8 @@ import core.game.world.map.Location import core.game.world.map.RegionManager import core.game.world.map.path.Pathfinder import core.tools.RandomFunction +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import rs09.game.content.activity.pestcontrol.PestControlHelper.GATE_ENTRIES import rs09.game.content.activity.pestcontrol.PestControlHelper.getMyPestControlSession1 import rs09.game.world.GameWorld @@ -100,7 +102,7 @@ class CombatState(val bot: PestControlTestBot) { fun randomWalkTo(loc: Location, radius: Int) { if(!bot.walkingQueue.isMoving) { - Executors.newSingleThreadExecutor().execute { + GlobalScope.launch { var newloc = loc.transform(RandomFunction.random(radius, -radius), RandomFunction.random(radius, -radius), 0) walkToIterator(newloc) diff --git a/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatStateIntermediate.kt b/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatStateIntermediate.kt index 8a313ccee..70243a700 100644 --- a/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatStateIntermediate.kt +++ b/Server/src/main/kotlin/rs09/game/ai/minigamebots/pestcontrol/CombatStateIntermediate.kt @@ -7,6 +7,8 @@ import core.game.world.map.Location import core.game.world.map.RegionManager import core.game.world.map.path.Pathfinder import core.tools.RandomFunction +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import rs09.game.content.activity.pestcontrol.PestControlHelper.GATE_ENTRIES import rs09.game.content.activity.pestcontrol.PestControlHelper.getMyPestControlSession2 import rs09.game.world.GameWorld @@ -99,7 +101,8 @@ class CombatStateIntermediate(val bot: PestControlTestBot2) { fun randomWalkTo(loc: Location, radius: Int) { if(!bot.walkingQueue.isMoving) { - Executors.newSingleThreadExecutor().execute { + GlobalScope.launch { + Thread.currentThread().name = "RandomWalkToIteratorBot" var newloc = loc.transform(RandomFunction.random(radius, -radius), RandomFunction.random(radius, -radius), 0) walkToIterator(newloc) diff --git a/Server/src/main/kotlin/rs09/game/content/ame/RandomEventNPC.kt b/Server/src/main/kotlin/rs09/game/content/ame/RandomEventNPC.kt index f82d75100..7ceb1cf3b 100644 --- a/Server/src/main/kotlin/rs09/game/content/ame/RandomEventNPC.kt +++ b/Server/src/main/kotlin/rs09/game/content/ame/RandomEventNPC.kt @@ -1,5 +1,6 @@ package rs09.game.content.ame +import api.ContentAPI import core.game.interaction.MovementPulse import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player @@ -36,18 +37,7 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { open fun terminate(){ player.antiMacroHandler.event = null if(initialized){ - Pulser.submit(object : Pulse(){ - var counter = 0 - override fun pulse(): Boolean { - when(counter++){ - 2 -> { - isInvisible = true; Graphics.send(SMOKE_GRAPHICS, location) - } - 3 -> clear().also { return true } - } - return false - } - }) + ContentAPI.poofClear(this) } } @@ -73,9 +63,9 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { } override fun init() { + initialized = true spawnLocation ?: terminate() location = spawnLocation - initialized = true super.init() } diff --git a/Server/src/main/kotlin/rs09/game/content/ame/events/certer/CerterDialogue.kt b/Server/src/main/kotlin/rs09/game/content/ame/events/certer/CerterDialogue.kt index 728480e8e..b29c231f8 100644 --- a/Server/src/main/kotlin/rs09/game/content/ame/events/certer/CerterDialogue.kt +++ b/Server/src/main/kotlin/rs09/game/content/ame/events/certer/CerterDialogue.kt @@ -1,6 +1,8 @@ package rs09.game.content.ame.events.certer +import api.ContentAPI import core.game.component.Component +import core.game.node.item.GroundItemManager import rs09.game.content.dialogue.DialogueFile import rs09.tools.END_DIALOGUE @@ -21,7 +23,7 @@ class CerterDialogue(val initial: Boolean) : DialogueFile() { 0 -> if(!isCorrect) npc("Sorry, I don't think so.").also { stage = END_DIALOGUE; player!!.antiMacroHandler.event?.terminate() } else npc("Oh yes! That's right.").also { stage++ } 1 -> { npc("Please, take this as a thanks.") - player!!.antiMacroHandler.event?.loot!!.roll(player!!).forEach { player!!.inventory.add(it) } + player!!.antiMacroHandler.event?.loot!!.roll().forEach { ContentAPI.addItemOrDrop(player!!, it.id, it.amount) } player!!.antiMacroHandler.event?.terminate() stage = END_DIALOGUE } diff --git a/Server/src/main/kotlin/rs09/game/content/dialogue/DialogueFile.kt b/Server/src/main/kotlin/rs09/game/content/dialogue/DialogueFile.kt index 9e0cdffa6..f120909d7 100644 --- a/Server/src/main/kotlin/rs09/game/content/dialogue/DialogueFile.kt +++ b/Server/src/main/kotlin/rs09/game/content/dialogue/DialogueFile.kt @@ -75,7 +75,7 @@ abstract class DialogueFile { return player(expr, *splitLines(msg!!)) } - fun end(){ + open fun end(){ if(interpreter != null) interpreter!!.close() } diff --git a/Server/src/main/kotlin/rs09/game/content/zone/FarmingPatchZone.kt b/Server/src/main/kotlin/rs09/game/content/zone/FarmingPatchZone.kt new file mode 100644 index 000000000..68145204b --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/zone/FarmingPatchZone.kt @@ -0,0 +1,98 @@ +package rs09.game.content.zone + +import api.ContentAPI +import core.game.content.dialogue.FacialExpression +import core.game.node.entity.Entity +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.system.task.Pulse +import core.game.world.map.zone.MapZone +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneBuilder +import core.plugin.Initializable +import core.plugin.Plugin +import org.rs09.consts.NPCs +import rs09.game.content.dialogue.DialogueFile +import rs09.tools.secondsToTicks +import java.util.concurrent.TimeUnit + +@Initializable +class FarmingPatchZone : MapZone("farming patch", true), Plugin { + + val playersInZone = hashMapOf() + + override fun configure() { + registerRegion(12083) + registerRegion(10548) + register(ZoneBorders(3594,3521,3608,3532)) + ContentAPI.submitWorldPulse(zonePulse) + } + + override fun newInstance(arg: Any?): Plugin { + ZoneBuilder.configure(this) + return this + } + + override fun fireEvent(identifier: String?, vararg args: Any?): Any { + return Unit + } + + override fun enter(e: Entity?): Boolean { + if(e is Player && playersInZone[e] == null && ContentAPI.getStatLevel(e, Skills.FARMING) <= 15) { + playersInZone[e] = 0 + } + return super.enter(e) + } + + override fun leave(e: Entity?, logout: Boolean): Boolean { + if(e is Player){ + playersInZone.remove(e) + } + return super.enter(e) + } + + val zonePulse = object : Pulse(){ + override fun pulse(): Boolean { + playersInZone.toList().forEach { (player, ticks) -> + if(ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(5).toInt())){ + val npc = NPC(NPCs.GITHAN_7122) + npc.location = player.location + npc.init() + npc.moveStep() + npc.face(player) + ContentAPI.openDialogue(player, SpiritDialogue(true), npc) + } else if (ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(10).toInt())){ + val npc = NPC(NPCs.GITHAN_7122) + npc.location = player.location + npc.init() + npc.moveStep() + npc.face(player) + ContentAPI.openDialogue(player, SpiritDialogue(false), npc) + playersInZone.remove(player) + } + + playersInZone[player] = ticks + 1 + } + return false + } + } + + internal class SpiritDialogue(val is5: Boolean) : DialogueFile(){ + override fun handle(componentID: Int, buttonID: Int) { + when(stage){ + 0 -> { + if(is5) npcl(FacialExpression.NEUTRAL, "In case you didn't know, you don't have to stand by your Farming patch. Your crops will grow even if you're not around.").also { stage++ } + else npcl(FacialExpression.NEUTRAL, " Did you know that if your Farming patch has fully grown, it will never catch disease or die? It will be perfectly safe until you choose to harvest it.").also { stage++ } + } + 1 -> end() + } + } + + override fun end(){ + super.end() + ContentAPI.poofClear(npc ?: return) + } + } + +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt index 3698bd93d..892394d6b 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt @@ -1,5 +1,6 @@ package rs09.game.node.entity.skill.farming +import api.ContentAPI import core.cache.def.impl.SceneryDefinition import core.game.interaction.OptionHandler import core.game.node.Node @@ -66,12 +67,8 @@ class CropHarvester : OptionHandler() { delay = 2 player.inventory.add(Item(plantable.harvestItem,1)) player.skills.addExperience(Skills.FARMING,plantable.harvestXP) - patch.harvestAmt-- - - if(patch.harvestAmt <= 0){ - patch.clear() - } - return patch.harvestAmt <= 0 + patch.rollLivesDecrement(ContentAPI.getDynLevel(player, Skills.FARMING), requiredItem == Items.MAGIC_SECATEURS_7409) + return patch.cropLives <= 0 } }) diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt index 19aae22ab..e178d8ae8 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt @@ -4,6 +4,8 @@ import core.game.node.entity.player.Player import core.tools.RandomFunction import rs09.game.system.SystemLogger import java.util.concurrent.TimeUnit +import kotlin.math.ceil +import kotlin.math.min class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantable?, var currentGrowthStage: Int, var isDiseased: Boolean, var isDead: Boolean, var isWatered: Boolean, var nextGrowth: Long, var harvestAmt: Int, var isCheckHealth: Boolean) { constructor(player: Player, patch: FarmingPatch) : this(player,patch,null,0,false,false,false,0L,0,false) @@ -11,22 +13,58 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl var diseaseMod = 0 var compost = CompostType.NONE var protectionPaid = false + var cropLives = 3 fun setNewHarvestAmount() { - if(patch.type == PatchType.ALLOTMENT){ - harvestAmt = RandomFunction.random(4,17) - } else if(patch.type == PatchType.FLOWER || patch.type == PatchType.EVIL_TURNIP) { - harvestAmt = when (plantable) { - Plantable.LIMPWURT_SEED, Plantable.WOAD_SEED -> 3 - else -> 1 + cropLives = 3 + } + + fun rollLivesDecrement(farmingLevel: Int, magicSecateurs: Boolean){ + if(patch.type == PatchType.HERB){ + //authentic formula thanks to released data. + var herbSaveLow = when(plantable){ + Plantable.GUAM_SEED -> min(24 + farmingLevel, 80) + Plantable.MARRENTILL_SEED -> min(28 + farmingLevel, 80) + Plantable.TARROMIN_SEED -> min(31 + farmingLevel, 80) + Plantable.HARRALANDER_SEED -> min(36 + farmingLevel, 80) + Plantable.GOUT_TUBER -> min(39 + farmingLevel, 80) + Plantable.RANARR_SEED -> min(39 + farmingLevel, 80) + Plantable.SPIRIT_WEED_SEED -> min(43 + farmingLevel, 80) + Plantable.TOADFLAX_SEED -> min(43 + farmingLevel, 80) + Plantable.IRIT_SEED -> min(46 + farmingLevel, 80) + Plantable.AVANTOE_SEED -> min(50 + farmingLevel, 80) + Plantable.KWUARM_SEED -> min(54 + farmingLevel, 80) + Plantable.SNAPDRAGON_SEED -> min(57 + farmingLevel, 80) + Plantable.CADANTINE_SEED -> min(60 + farmingLevel, 80) + Plantable.LANTADYME_SEED -> min(64 + farmingLevel, 80) + Plantable.DWARF_WEED_SEED -> min(67 + farmingLevel, 80) + Plantable.TORSTOL_SEED -> min(71 + farmingLevel, 80) + else -> -1 + } + + if(magicSecateurs) herbSaveLow = ceil(1.10 * herbSaveLow).toInt() + + val rand = RandomFunction.random(256) + + if(rand > herbSaveLow){ + cropLives -= 1 } - } else if(patch.type == PatchType.HOPS){ - harvestAmt = RandomFunction.random(3,35) } else { - harvestAmt = RandomFunction.random(3,5) + //inauthentic formulae based on reported averages due to lack of formula + var chance = when(patch.type){ + PatchType.ALLOTMENT -> 8 //average of 8 per life times 3 lives = average 24 + PatchType.HOPS -> 6 //average of 6 per life times 3 lives = 18 + PatchType.BELLADONNA -> 2 //average of 2 per life times 3 lives = 6 + PatchType.CACTUS -> 3 //average of 3 per life times 3 lives = 9 + else -> return + } + + if(magicSecateurs) chance += ceil(1.10 * chance).toInt() //will increase average yield by roughly 3. + + if(RandomFunction.roll(chance)) cropLives -= 1 } - if(compost == CompostType.NORMAL) harvestAmt += 1 - if(compost == CompostType.SUPER) harvestAmt += 2 + + if(cropLives <= 0) clear() } fun isWeedy(): Boolean { @@ -82,6 +120,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl if(isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getCactusDeathValue()) else if(isDiseased && !isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getCactusDiseaseValue()) } + PatchType.HERB -> { + if(isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getHerbDeathValue()) + else if(isDiseased && !isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getHerbDiseaseValue()) + else player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, (plantable?.value ?: 0) + currentGrowthStage) + } else -> {} } } @@ -144,6 +187,22 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl return (plantable?.value ?: 0) + currentGrowthStage + 16 } + private fun getHerbDiseaseValue(): Int { + return if (plantable?.value ?: -1 <= 103) { + 128 + (((plantable?.ordinal ?: 0) - Plantable.GUAM_SEED.ordinal) * 3) + currentGrowthStage - 1 + } else if (plantable == Plantable.SPIRIT_WEED_SEED) { + 211 + currentGrowthStage - 1 + } else { + 198 + currentGrowthStage -1 + } + } + + private fun getHerbDeathValue(): Int { + return if(plantable == Plantable.GOUT_TUBER){ + 201 + currentGrowthStage - 1 + } else 170 + currentGrowthStage - 1 + } + private fun grow(){ if(isWeedy() && getCurrentState() > 0) { nextGrowth = System.currentTimeMillis() + 60000 @@ -164,8 +223,8 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl } if(RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP){ - //bush, tree, fruit tree and cactus can not disease on stage 1(0) of growth. - if(!((patch.type == PatchType.BUSH || patch.type == PatchType.TREE || patch.type == PatchType.FRUIT_TREE || patch.type == PatchType.CACTUS) && currentGrowthStage == 0)) { + //bush, tree, fruit tree, herb and cactus can not disease on stage 1(0) of growth. + if(!((patch.type == PatchType.BUSH || patch.type == PatchType.TREE || patch.type == PatchType.FRUIT_TREE || patch.type == PatchType.CACTUS || patch.type == PatchType.HERB) && currentGrowthStage == 0)) { isDiseased = true return } diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Plantable.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Plantable.kt index 8cf594791..8921590d3 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Plantable.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Plantable.kt @@ -63,17 +63,18 @@ enum class Plantable(val itemID: Int, val value: Int, val stages: Int, val plant MARRENTILL_SEED(5292,11,4,13.5,15.0,0.0,14,PatchType.HERB,Items.GRIMY_MARRENTILL_201), TARROMIN_SEED(5293,18,4,16.0,18.0,0.0,19,PatchType.HERB,Items.GRIMY_TARROMIN_203), HARRALANDER_SEED(5294,25,4,21.5,24.0,0.0,26,PatchType.HERB,Items.GRIMY_HARRALANDER_205), - GOUT_TUBER(6311,32,4,105.0,45.0,0.0,29,PatchType.HERB,Items.GOUTWEED_3261), - RANARR_SEED(5295,39,4,27.0,30.5,0.0,32,PatchType.HERB,Items.GRIMY_RANARR_207), - TOADFLAX_SEED(5296,47,4,34.0,38.5,0.0,38,PatchType.HERB,Items.GRIMY_TOADFLAX_3049), - IRIT_SEED(5297,54,4,43.0,48.5,0.0,44,PatchType.HERB,Items.GRIMY_IRIT_209), - AVANTOE_SEED(5298,204,4,54.5,61.5,0.0,50,PatchType.HERB,Items.GRIMY_AVANTOE_211), + RANARR_SEED(5295,32,4,27.0,30.5,0.0,32,PatchType.HERB,Items.GRIMY_RANARR_207), + AVANTOE_SEED(5298,39,4,54.5,61.5,0.0,50,PatchType.HERB,Items.GRIMY_AVANTOE_211), + TOADFLAX_SEED(5296,46,4,34.0,38.5,0.0,38,PatchType.HERB,Items.GRIMY_TOADFLAX_3049), + IRIT_SEED(5297,53,4,43.0,48.5,0.0,44,PatchType.HERB,Items.GRIMY_IRIT_209), KWUARM_SEED(5299,68,4,69.0,78.0,0.0,56,PatchType.HERB,Items.GRIMY_KWUARM_213), SNAPDRAGON_SEED(5300,75,4,87.5,98.5,0.0,62,PatchType.HERB,Items.GRIMY_SNAPDRAGON_3051), CADANTINE_SEED(5301,82,4,106.5,120.0,0.0,67,PatchType.HERB,Items.GRIMY_CADANTINE_215), LANTADYME_SEED(5302,89,4,134.5,151.5,0.0,73,PatchType.HERB,Items.GRIMY_LANTADYME_2485), DWARF_WEED_SEED(5303,96,4,170.5,192.0,0.0,79,PatchType.HERB,Items.GRIMY_DWARF_WEED_217), TORSTOL_SEED(5304,103,4,199.5,224.5,0.0,85,PatchType.HERB,Items.GRIMY_TORSTOL_219), + GOUT_TUBER(6311,192,4,105.0,45.0,0.0,29,PatchType.HERB,Items.GOUTWEED_3261), + SPIRIT_WEED_SEED(12176, 204, 4, 32.0, 36.0, 0.0, 36, PatchType.HERB, Items.GRIMY_SPIRIT_WEED_12174), //Other BELLADONNA_SEED(5281, 4, 4, 91.0, 128.0, 0.0, 63, PatchType.BELLADONNA, Items.CAVE_NIGHTSHADE_2398), diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt index 1d45b2b70..22d160a93 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt @@ -128,6 +128,7 @@ object UseWithPatchHandler{ if(p.plantable != null){ p.harvestAmt += if(p.compost == CompostType.NORMAL) 1 else if(p.compost == CompostType.SUPER) 2 else 0 } + p.cropLives += if(p.compost == CompostType.SUPER) 2 else 1 player.inventory.add(Item(Items.BUCKET_1925)) } return true diff --git a/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt b/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt index e5ae69574..2024508a9 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt @@ -50,6 +50,7 @@ class FarmingState(player: Player? = null) : State(player) { p.put("patch-checkHealth",patch.isCheckHealth) p.put("patch-compost",patch.compost.ordinal) p.put("patch-paidprot",patch.protectionPaid) + p.put("patch-croplives", patch.cropLives) patches.add(p) } val bins = JSONArray() @@ -91,10 +92,12 @@ class FarmingState(player: Player? = null) : State(player) { val savedState = p["patch-state"].toString().toInt() val compostOrdinal = p["patch-compost"].toString().toInt() val protectionPaid = p["patch-paidprot"] as Boolean + val cropLives = if(p["patch-croplives"] != null) p["patch-croplives"].toString().toInt() else 3 val fPatch = FarmingPatch.values()[patchOrdinal] val plantable = if(patchPlantableOrdinal != -1) Plantable.values()[patchPlantableOrdinal] else null val patch = Patch(player,fPatch,plantable,patchStage,patchDiseased,patchDead,patchWatered,nextGrowth,harvestAmt,checkHealth) + patch.cropLives = cropLives patch.compost = CompostType.values()[compostOrdinal] patch.protectionPaid = protectionPaid patch.setCurrentState(savedState) diff --git a/Server/src/main/kotlin/rs09/game/world/ImmerseWorld.kt b/Server/src/main/kotlin/rs09/game/world/ImmerseWorld.kt index cdfc035bb..4d5af3e6a 100644 --- a/Server/src/main/kotlin/rs09/game/world/ImmerseWorld.kt +++ b/Server/src/main/kotlin/rs09/game/world/ImmerseWorld.kt @@ -15,6 +15,7 @@ object ImmerseWorld { @JvmStatic fun init() { Executors.newSingleThreadExecutor().execute { + Thread.currentThread().name = "BotSpawner" immerseSeersAndCatherby() immerseLumbridgeDraynor() immerseVarrock() diff --git a/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt b/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt index 864d2663e..843fc2237 100644 --- a/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt +++ b/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt @@ -2,9 +2,6 @@ package rs09.game.world.repository import core.game.node.entity.player.Player import core.game.node.entity.player.info.login.PlayerParser -import core.game.system.mysql.SQLEntryHandler -import core.game.system.mysql.impl.HighscoreSQLHandler -import core.game.system.mysql.impl.PlayerLogSQLHandler import core.game.system.task.TaskExecutor import rs09.game.system.SystemLogger import rs09.game.world.GameWorld @@ -71,7 +68,10 @@ class DisconnectionQueue { return true } if (!force) { - TaskExecutor.executeSQL { save(player, true) } + TaskExecutor.executeSQL { + Thread.currentThread().name = "PlayerSave SQL" + save(player, true) + } return true } save(player, false) diff --git a/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt b/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt index 5d13af290..4a16fef80 100644 --- a/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt +++ b/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt @@ -8,6 +8,8 @@ import core.game.world.repository.InitializingNodeList import core.net.packet.PacketRepository import core.net.packet.context.PlayerContext import core.net.packet.out.ClearMinimapFlag +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import rs09.game.world.repository.Repository import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors @@ -58,7 +60,7 @@ class UpdateSequence fun run() { val latch = CountDownLatch(renderablePlayers.size) playersList!!.forEach(Consumer { player: Player -> - EXECUTOR.execute { + GlobalScope.launch { try { player.update() } catch (t: Throwable) { @@ -89,7 +91,6 @@ class UpdateSequence * Terminates the update sequence. */ fun terminate() { - EXECUTOR.shutdown() } companion object { @@ -103,11 +104,5 @@ class UpdateSequence @JvmStatic val renderablePlayers = InitializingNodeList() - /** - * The executor used. - */ - @JvmStatic - private val EXECUTOR = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) - } } \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt b/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt index d6fae103f..1dfcecf72 100644 --- a/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt +++ b/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt @@ -103,21 +103,22 @@ class LoginReadEvent session.isaacPair = ISAACPair(inCipher, outCipher) session.clientInfo = ClientInfo(displayMode, windowMode, screenWidth, screenHeight) val b = buffer - TaskExecutor.executeSQL(Runnable { + TaskExecutor.executeSQL { + Thread.currentThread().name = "Login Password Response" try { val username = StringUtils.longToString(b.long) val password = ByteBufferUtils.getString(b) val response = PlayerSQLManager.getCredentialResponse(username, password) if (response != Response.SUCCESSFUL) { session.write(response, true) - return@Runnable + return@executeSQL } login(PlayerDetails(username, password), session, b, opcode) } catch (e: Exception) { e.printStackTrace() session.write(Response.COULD_NOT_LOGIN) } - }) + } } /** @@ -138,7 +139,7 @@ class LoginReadEvent if (WorldCommunicator.isEnabled()) { WorldCommunicator.register(parser) } else { - TaskExecutor.executeSQL(parser) + TaskExecutor.executeSQL {parser.run()} } }