Fixed issue preventing pickup of dwarf remains in Dwarf Cannon

Added more sophisticated debug varp printing
Fixed issue preventing surprise exam rewards if player moves before reward is granted
Voluntarily leaving a clan should now persist
Fixed bug that would result in keeping the previous wand when purchasing MTA upgrade
Fixed issue preventing proper teleport jewellery usage
Fixed a bug that prevented manual casting of Ancient spells
Added many sanity checks to death code and fixed a bug with delayed impacts
Added dragon slayer requirements to Green (g) and Green (t) bodies
Rewrote the "catchup" logic used for growing farming patches during missed playtime
Fixed bug preventing fruit trees and berries from growing while logged out
Summoning potion now correctly restores 25% of summoning special energy
This commit is contained in:
Ceikry
2023-01-24 01:37:39 +00:00
committed by Ryan
parent dd5e63a409
commit 4e6e2e678d
24 changed files with 129 additions and 61 deletions
@@ -17,6 +17,7 @@ import core.plugin.Plugin;
import core.tools.RandomFunction;
import kotlin.Unit;
import rs09.game.content.global.action.EquipHandler;
import rs09.game.node.entity.player.info.LogType;
import rs09.game.node.entity.player.info.PlayerMonitor;
import rs09.game.system.config.ItemConfigParser;
@@ -311,7 +312,7 @@ public final class DuelSession extends ComponentPlugin {
log = log.substring(0, log.length() - 1);
}
log += "}";
PlayerMonitor.logMisc(player, "Duel", log);
PlayerMonitor.log(player, LogType.DUEL_INFO, log);
} else {
player.removeExtension(DuelSession.class);
}
@@ -1,5 +1,6 @@
package core.game.content.activity.mta;
import api.ContainerisedItem;
import core.game.component.CloseEvent;
import core.game.component.Component;
import core.game.component.ComponentPlugin;
@@ -15,6 +16,8 @@ import rs09.game.world.GameWorld;
import java.util.ArrayList;
import java.util.List;
import static api.ContentAPIKt.hasAnItem;
/**
* Represents the mage training arena shop.
* @author Vexia
@@ -147,9 +150,11 @@ public class MTAShop {
player.sendMessage("You already unlocked that spell.");
return;
}
ContainerisedItem itemToRemove = null;
if (slot >= 1 && slot <= 3) {
Item required = ITEMS[slot - 1];
if (!player.hasItem(new Item(required.getId(), 1)) && !player.hasItem(new Item(6914, 1))) {
itemToRemove = hasAnItem(player, required.getId());
if (!itemToRemove.exists() && !player.hasItem(new Item(6914, 1))) {
player.sendMessage("You don't have the required wand in order to buy this upgrade.");
return;
}
@@ -163,7 +168,8 @@ public class MTAShop {
player.getSavedData().getActivityData().setBonesToPeaches(true);
player.getDialogueInterpreter().sendDialogue("The guardian teaches you how to use the Bones to Peaches spell!");
} else {
player.getInventory().add(item);
if (itemToRemove == null || itemToRemove.remove())
player.getInventory().add(item);
}
for (int i = 0; i < prices.length; i++) {
decrementPoints(player, i, prices[i]);
@@ -7,6 +7,7 @@ import core.game.world.update.flag.context.Animation;
import core.game.content.consumable.effects.*;
import core.game.node.entity.skill.Skills;
import rs09.game.content.consumable.effects.KegOfBeerEffect;
import rs09.game.content.consumable.effects.RestoreSummoningSpecial;
import java.util.HashMap;
@@ -321,7 +322,7 @@ public enum Consumables {
HUNTER(new Potion(new int[] {9998, 10000, 10002, 10004}, new SkillEffect(Skills.HUNTER, 3, 0))),
RESTORE(new Potion(new int[] {2430, 127, 129, 131}, new RestoreEffect(10, 0.3))),
SARA_BREW(new Potion(new int[] {6685, 6687, 6689, 6691}, new MultiEffect(new PercentHeal(2, .15), new SkillEffect(Skills.ATTACK, 0, -0.10), new SkillEffect(Skills.STRENGTH, 0, -0.10), new SkillEffect(Skills.MAGIC, 0, -0.10), new SkillEffect(Skills.RANGE, 0, -0.10), new SkillEffect(Skills.DEFENCE, 2, 0.2)))),
SUMMONING(new Potion(new int[] {12140, 12142, 12144, 12146}, new SummoningEffect(7, 0.25))),
SUMMONING(new Potion(new int[] {12140, 12142, 12144, 12146}, new MultiEffect(new RestoreSummoningSpecial(), new SummoningEffect(7, 0.25)))),
COMBAT(new Potion(new int[] {9739, 9741, 9743, 9745}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 3, .1), new SkillEffect(Skills.ATTACK, 3, .1)))),
ENERGY(new Potion(new int[] {3008, 3010, 3012, 3014}, new MultiEffect(new EnergyEffect(10), new HealingEffect(3)))),
FISHING(new Potion(new int[] {2438, 151, 153, 155}, new SkillEffect(Skills.FISHING, 3, 0))),
@@ -338,6 +338,8 @@ public abstract class Entity extends Node {
* @param state The battle state.
*/
public void onImpact(final Entity entity, BattleState state) {
if (DeathTask.isDead(this))
state.neutralizeHits();
if (properties.isRetaliating() && !properties.getCombatPulse().isAttacking() && !getLocks().isInteractionLocked() && properties.getCombatPulse().getNextAttack() < GameWorld.getTicks()) {
if (!getWalkingQueue().hasPath() && !getPulseManager().isMovingPulse() || (this instanceof NPC)) {
properties.getCombatPulse().attack(entity);
@@ -138,6 +138,11 @@ public final class ImpactHandler {
* @return The impact object created.
*/
public Impact handleImpact(Entity source, int hit, final CombatStyle style, final BattleState state, HitsplatType type, boolean secondary) {
if (DeathTask.isDead(this.entity)) {
this.entity.getProperties().getCombatPulse().setVictim(null);
this.entity.getProperties().getCombatPulse().stop();
return null;
}
boolean fam = source instanceof Familiar;
if (fam) {
source = ((Familiar) source).getOwner();
@@ -394,6 +394,8 @@ public class NPC extends Entity {
return;
}
if (respawnTick > GameWorld.getTicks()) {
if (respawnTick - GameWorld.getTicks() == 2)
teleport(getProperties().getSpawnLocation());
return;
}
if (respawnTick == GameWorld.getTicks()) {
@@ -522,9 +524,8 @@ public class NPC extends Entity {
GlobalKillCounter.incrementKills(p, originalId);
}
handleDrops(p, killer);
if (!isRespawn()) {
if (!isRespawn())
clear();
}
killer.dispatch(new NPCKillEvent(this));
}
@@ -65,13 +65,12 @@ import proto.management.ClanLeaveNotification;
import proto.management.PlayerStatusUpdate;
import rs09.GlobalStats;
import rs09.ServerConstants;
import rs09.game.Varp;
import rs09.game.VarpManager;
import rs09.game.node.entity.combat.CombatSwingHandler;
import rs09.game.node.entity.combat.equipment.EquipmentDegrader;
import rs09.game.node.entity.player.graves.Grave;
import rs09.game.node.entity.player.graves.GraveType;
import rs09.game.node.entity.player.graves.GraveController;
import rs09.game.node.entity.player.info.LogType;
import rs09.game.node.entity.player.info.PlayerMonitor;
import rs09.game.node.entity.player.info.login.PlayerSaver;
import rs09.game.node.entity.skill.runecrafting.PouchManager;
@@ -654,7 +653,7 @@ public class Player extends Entity {
GroundItemManager.create(item, location, killer instanceof Player ? (Player) killer : this);
}
if (killer instanceof Player)
PlayerMonitor.logMisc((Player) killer, "PK", "Killed " + name + ", who dropped: " + itemsLost);
PlayerMonitor.log((Player) killer, LogType.PK, "Killed " + name + ", who dropped: " + itemsLost);
sendMessage(colorize("%RDue to the circumstances of your death, you do not have a grave."));
}
@@ -103,6 +103,7 @@ public abstract class NodeTask {
return NodeTask.this.removeFor(s, node, n);
}
};
pulse.start();
return pulse;
}
@@ -603,8 +603,17 @@ object RegionManager {
val it = npcs.iterator()
while (it.hasNext()) {
val p = it.next()
if(p.properties.teleportLocation != null && !p.properties.teleportLocation.withinMaxnormDistance(n.location, 1)) {
it.remove()
continue
}
if(p.getAttribute("state:death", false)) {
it.remove()
continue
}
if(p.isInvisible()) {
it.remove()
continue
}
if(!p.location.withinMaxnormDistance(n.location, 1)) {
it.remove()
+25 -3
View File
@@ -55,6 +55,8 @@ import rs09.game.content.global.shops.Shops
import rs09.game.ge.GrandExchangeRecords
import rs09.game.interaction.InteractionListeners
import rs09.game.interaction.inter.ge.StockMarket
import rs09.game.node.entity.player.info.LogType
import rs09.game.node.entity.player.info.PlayerMonitor
import rs09.game.node.entity.skill.slayer.SlayerManager
import rs09.game.system.SystemLogger
import rs09.game.system.config.ItemConfigParser
@@ -164,7 +166,14 @@ fun areAnyEquipped(player: Player, vararg ids: Int): Boolean {
}
}
data class ContainerisedItem(val container: core.game.container.Container?, val itemId: Int)
class ContainerisedItem(val container: core.game.container.Container?, val itemId: Int) {
fun remove() : Boolean {
return this.container?.remove(this.itemId.asItem()) ?: false
}
fun exists() : Boolean {
return this.container != null && this.itemId > -1
}
}
/**
* Check if player has any of the specified item IDs equipped, in inventory, or in banks
@@ -239,14 +248,27 @@ fun addItem(player: Player, id: Int, amount: Int = 1, container: Container = Con
* @param container the Container to modify
* @return the item that was previously in the slot, or null if none.
*/
fun replaceSlot(player: Player, slot: Int, item: Item, container: Container = Container.INVENTORY): Item? {
fun replaceSlot(player: Player, slot: Int, item: Item, currentItem: Item? = null, container: Container = Container.INVENTORY): Item? {
val cont = when (container) {
Container.INVENTORY -> player.inventory
Container.EQUIPMENT -> player.equipment
Container.BANK -> player.bank
}
return cont.replace(item, slot)
if (currentItem == null) {
return cont.replace(item, slot)
}
if (cont.remove(currentItem))
return cont.replace(item, slot)
PlayerMonitor.log(player, LogType.DUPE_ALERT, "Potential slot-replacement-based dupe attempt, slot: $slot, item: $item")
val other = when (container) {
Container.INVENTORY -> Container.EQUIPMENT
else -> Container.INVENTORY
}
if (removeItem(player, currentItem, other))
return cont.replace(item, slot)
return null
}
/**
@@ -1,6 +1,7 @@
package rs09.game.content.ame.events.supriseexam
import api.*
import core.game.node.entity.impl.PulseType
import core.game.node.entity.player.Player
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
@@ -51,7 +52,7 @@ object SurpriseExamUtils {
}
return true
}
})
}, PulseType.CUSTOM_1)
}
fun generateInterface(player: Player){
@@ -1,13 +0,0 @@
package rs09.game.content.consumable.effects
import core.game.node.entity.player.Player
import core.game.content.consumable.ConsumableEffect
import core.game.node.entity.skill.Skills
class RestoreSummoning(val percent: Double) : ConsumableEffect(){
override fun activate(p: Player?) {
var amt = (p!!.skills!!.getStaticLevel(Skills.SUMMONING) * percent).toInt()
if(amt + p.skills.getLevel(Skills.SUMMONING) > p.skills.getStaticLevel(Skills.SUMMONING)) amt = p.skills.getStaticLevel(Skills.SUMMONING) - p.skills.getLevel(Skills.SUMMONING)
p.skills.setLevel(Skills.SUMMONING, p.skills.getLevel(Skills.SUMMONING) + amt)
}
}
@@ -0,0 +1,11 @@
package rs09.game.content.consumable.effects
import core.game.content.consumable.ConsumableEffect
import core.game.node.entity.player.Player
class RestoreSummoningSpecial : ConsumableEffect(){
override fun activate(p: Player) {
val f = p.familiarManager.familiar
f?.updateSpecialPoints(-15)
}
}
@@ -14,7 +14,7 @@ import rs09.tools.END_DIALOGUE
*/
@Initializable
class AlKharidShopKeeperDialogue(player: Player? = null) : DialoguePlugin() {
class AlKharidShopKeeperDialogue(player: Player? = null) : DialoguePlugin(player) {
override fun newInstance(player: Player?): DialoguePlugin {
return AlKharidShopKeeperDialogue(player)
}
@@ -249,7 +249,7 @@ enum class EnchantedJewellery(
private fun replaceJewellery(player: Player, item: Item, nextJewellery: Item, isEquipped: Boolean) {
if (isEquipped) {
replaceSlot(player, item.slot, nextJewellery, Container.EQUIPMENT)
replaceSlot(player, item.slot, nextJewellery, item, Container.EQUIPMENT)
} else {
replaceSlot(player, item.slot, nextJewellery)
}
@@ -6,14 +6,13 @@ import core.game.content.dialogue.FacialExpression
import core.game.content.global.GodType
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.audio.Audio
import core.game.node.entity.player.link.diary.DiaryType
import core.game.node.entity.skill.runecrafting.RunePouch
import core.game.node.item.GroundItem
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.world.map.RegionManager
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import rs09.game.node.entity.player.info.LogType
import rs09.game.node.entity.player.info.PlayerMonitor
import rs09.game.system.SystemLogger
import rs09.game.system.config.GroundSpawnLoader
@@ -58,7 +57,7 @@ object PickupHandler {
}
if (item.isActive && player.inventory.add(add)) {
if (item.dropper is Player && item.dropper.details.uid != player.details.uid){
PlayerMonitor.logMisc(item.dropper, "DropTrade", "${getItemName(item.id)} x${item.amount} picked up by ${player.name}.")
PlayerMonitor.log(item.dropper, LogType.DROP_TRADE, "${getItemName(item.id)} x${item.amount} picked up by ${player.name}.")
}
if (!RegionManager.isTeleportPermitted(item.location)) {
player.animate(Animation.create(535))
@@ -83,7 +83,9 @@ class ItemQuestRequirementListener : InteractionListener {
Items.ZAMORAK_PLATEBODY_2653,
Items.SARADOMIN_PLATEBODY_2661,
Items.GUTHIX_PLATEBODY_2669,
Items.GREEN_DHIDE_BODY_1135
Items.GREEN_DHIDE_BODY_1135,
Items.DHIDE_BODYG_7370, //Green (g)
Items.DHIDE_BODY_T_7372 //Green (t)
)
private val questCapes = intArrayOf(9813,9814)
@@ -437,7 +437,7 @@ class CombatPulse(
GameWorld.Pulser.submit(object : Pulse(delay - 1, entity, victim) {
var impact = false
override fun pulse(): Boolean {
if (DeathTask.isDead(victim)) {
if (DeathTask.isDead(victim) || DeathTask.isDead(entity)) {
return true
}
if (impact || getDelay() == 0) {
@@ -81,11 +81,11 @@ object PlayerMonitor {
dispatch(event)
}
@JvmStatic fun logMisc(player: Player, type: String, details: String) {
@JvmStatic fun log(player: Player, type: LogType, details: String) {
val event = LogEvent.MiscLog(
player.name,
player.details.uid,
type,
type.token,
details,
System.currentTimeMillis()
)
@@ -253,4 +253,13 @@ object PlayerMonitor {
private const val MISC_LOG_INSERT = "INSERT INTO misc_logs(player,uid,type,details,timestamp) VALUES (?,?,?,?,?);"
private const val WEALTH_LOG_INSERT = "INSERT INTO wealth_logs(player,uid,total,diff,timestamp) VALUES (?,?,?,?,?);"
}
enum class LogType(val token: String) {
DUPE_ALERT("dupe_warning"),
DUEL_INFO("Duel"),
PK("PK"),
DROP_TRADE("DropTrade"),
COMMAND("CommandUsed"),
IP_LOG("login_ip")
}
@@ -317,6 +317,16 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
return getCurrentState() - plantable!!.value - plantable!!.stages
}
fun getStageGrowthMinutes() : Int {
var minutes = patch.type.stageGrowthTime
if(patch.type == PatchType.FRUIT_TREE && isGrown()) {
// Fruit trees take 160 minutes per stage to grow, but
// restocking their fruit should take 40 minutes per fruit
minutes = 40
}
return minutes
}
fun isFlowerProtected(): Boolean{
if(patch.type != PatchType.ALLOTMENT) return false
@@ -1,5 +1,6 @@
package rs09.game.node.entity.state.newsys.states
import core.Util.clamp
import core.game.node.entity.player.Player
import core.game.system.task.Pulse
import kotlinx.coroutines.GlobalScope
@@ -101,20 +102,6 @@ class FarmingState(player: Player? = null) : State(player) {
patch.compost = CompostType.values()[compostOrdinal]
patch.protectionPaid = protectionPaid
patch.setCurrentState(savedState)
if((patch.currentGrowthStage < patch.plantable?.stages ?: 0) && !patch.isWeedy()){
val startTime = (patch.nextGrowth - TimeUnit.MINUTES.toMillis(patch.patch.type.stageGrowthTime * (patch.currentGrowthStage + 1).toLong()))
var expectedStage = Math.floor((System.currentTimeMillis() - startTime.toDouble()) / TimeUnit.MINUTES.toMillis(patch.patch.type.stageGrowthTime.toLong())).toInt()
SystemLogger.logErr(this::class.java, "$expectedStage $startTime ${System.currentTimeMillis()}")
if(!patchDiseased && !patchDead) {
if (expectedStage > patch.plantable?.stages ?: 0) {
expectedStage = patch.plantable?.stages ?: 0
}
for(i in 0 until (expectedStage - patch.currentGrowthStage)){
patch.update()
}
}
}
if((savedState - (patch?.plantable?.value ?: 0)) > patch.currentGrowthStage){
patch.setCurrentState(savedState)
@@ -122,6 +109,25 @@ class FarmingState(player: Player? = null) : State(player) {
patch.setCurrentState((patch.plantable?.value ?: 0) + patch.currentGrowthStage)
}
val type = patch.patch.type
val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE && patch.getFruitOrBerryCount() < 6)
if(shouldPlayCatchup && patch.plantable != null && !patchDead){
var stagesToSimulate = if (!patch.isGrown()) patch.plantable!!.stages - patch.currentGrowthStage else 0
if (type == PatchType.BUSH)
stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount())
if (type == PatchType.FRUIT_TREE)
stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount())
val nowTime = System.currentTimeMillis()
var simulatedTime = patch.nextGrowth
while (simulatedTime < nowTime && stagesToSimulate-- > 0 && !patch.isDead) {
val timeToIncrement = TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong())
patch.update()
simulatedTime += timeToIncrement
}
}
if(patchMap[fPatch] == null) {
patchMap[fPatch] = patch
}
@@ -148,13 +154,7 @@ class FarmingState(player: Player? = null) : State(player) {
if(patch.nextGrowth < System.currentTimeMillis() && !patch.isDead){
patch.update()
var minutes = patch.patch.type.stageGrowthTime.toLong()
if(patch.patch.type == PatchType.FRUIT_TREE && patch.isGrown()) {
// Fruit trees take 160 minutes per stage to grow, but
// restocking their fruit should take 40 minutes per fruit
minutes = 40
}
patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(minutes)
patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong())
}
}
@@ -38,7 +38,6 @@ import core.net.amsc.MSPacketRepository
import core.net.packet.PacketRepository
import core.net.packet.context.PlayerContext
import core.net.packet.out.ClearMinimapFlag
import discord.Discord
import org.rs09.consts.Components
import proto.management.ClanMessage
import proto.management.JoinClanRequest
@@ -53,6 +52,7 @@ import rs09.game.interaction.InteractionListeners
import rs09.game.interaction.InterfaceListeners
import rs09.game.interaction.QCRepository
import rs09.game.interaction.inter.ge.StockMarket
import rs09.game.node.entity.player.info.LogType
import rs09.game.node.entity.player.info.PlayerMonitor
import rs09.game.node.entity.skill.magic.SpellListener
import rs09.game.node.entity.skill.magic.SpellListeners
@@ -199,7 +199,7 @@ object PacketProcessor {
pkt.player.interfaceManager.closeChatbox()
}
is Packet.Command -> {
PlayerMonitor.logMisc(pkt.player, "CommandUse", pkt.commandLine)
PlayerMonitor.log(pkt.player, LogType.COMMAND, pkt.commandLine)
CommandSystem.commandSystem.parse(pkt.player, pkt.commandLine)
}
is Packet.ChatMessage -> {
@@ -404,7 +404,7 @@ object PacketProcessor {
if (book != "none")
SpellListeners.run(child, type, book, player, target)
when (iface) {
430,192 -> MagicSpell.castSpell(player, SpellBookManager.SpellBook.forInterface(iface), child, target)
430,192,193 -> MagicSpell.castSpell(player, SpellBookManager.SpellBook.forInterface(iface), child, target)
662 -> {
if (player.familiarManager.hasFamiliar())
player.familiarManager.familiar.executeSpecialMove(FamiliarSpecial(target, iface, child, target as? Item))
@@ -664,7 +664,7 @@ object PacketProcessor {
player.debug("ID: ${wrapperChild.id}, Option: ${option.name}[${option.index}]")
player.debug("Loc: ${scenery.location}, Dir: ${scenery.direction}")
if (hasWrapper) {
player.debug("WrapperID: ${scenery.id}, Varbit: ${scenery.definition.configFile.id}")
player.debug("WrapperID: ${scenery.id}, ${scenery.definition.configFile?.let { "Varbit: ${it.id}"} ?: "Varp: ${scenery.definition.configId}"}")
}
player.debug("------------------------------------------------")
@@ -19,6 +19,7 @@ import rs09.ServerStore
import rs09.ServerStore.Companion.addToList
import rs09.ServerStore.Companion.getList
import rs09.auth.AuthResponse
import rs09.game.node.entity.player.info.LogType
import rs09.game.node.entity.player.info.PlayerMonitor
import rs09.game.node.entity.player.info.login.LoginParser
import rs09.game.system.SystemLogger
@@ -132,7 +133,7 @@ object Login {
}
val player = Player(details)
PlayerMonitor.logMisc(player, "login_ip", details.ipAddress)
PlayerMonitor.log(player, LogType.IP_LOG, details.ipAddress)
if (canBypassAccountLimitCheck(player)) {
proceedWithAcceptableLogin(session, player, opcode)
} else {
@@ -223,6 +223,7 @@ object ManagementEvents {
sendMessage(p, "Error leaving clan. Please relog.")
} else {
clan.leave(p, true)
p.details.communication.clan = null
}
}