diff --git a/.gitignore b/.gitignore index c852a9f9b..403765d89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .* +!.idea/ !.gitignore !.rubocop.yml !.editorconfig diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..cc1164cca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,4 @@ +* + +!.gitignore +!kotlinc.xml diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 000000000..a2e87d82d --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/buildSrc/src/main/groovy/org/apollo/build/plugin/ApolloPluginExtension.groovy b/buildSrc/src/main/groovy/org/apollo/build/plugin/ApolloPluginExtension.groovy index ea4c910e4..297007310 100644 --- a/buildSrc/src/main/groovy/org/apollo/build/plugin/ApolloPluginExtension.groovy +++ b/buildSrc/src/main/groovy/org/apollo/build/plugin/ApolloPluginExtension.groovy @@ -2,9 +2,7 @@ package org.apollo.build.plugin import org.apollo.build.plugin.tasks.ApolloScriptCompileTask import org.gradle.api.Project -import org.gradle.api.Task import org.gradle.api.file.FileTree -import org.gradle.api.tasks.testing.Test class ApolloPluginExtension { final Project project @@ -79,7 +77,7 @@ class ApolloPluginExtension { main { kotlin { srcDir this.srcDir - exclude '*.kts' + exclude '**/*.kts' } } @@ -98,7 +96,7 @@ class ApolloPluginExtension { def buildTask = project.tasks['classes'] FileTree scripts = project.fileTree(srcDir).matching { - include '*.kts' + include '**/*.kts' } project.tasks.create('compileScripts', ApolloScriptCompileTask) { diff --git a/game/plugin/api/src/org/apollo/game/plugins/api/attributes.kt b/game/plugin/api/src/org/apollo/game/plugins/api/attributes.kt new file mode 100644 index 000000000..ee1dfd786 --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugins/api/attributes.kt @@ -0,0 +1,45 @@ +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.attr.* +import kotlin.reflect.KClass +import kotlin.reflect.KProperty + +object MobAttributeDelegators { + inline fun attribute(name: String, defaultValue: T): MobAttributeDelegate { + return MobAttributeDelegate(name, T::class, defaultValue, false) + } + + inline fun persistentAttribute(name: String, defaultValue: T): MobAttributeDelegate { + return MobAttributeDelegate(name, T::class, defaultValue, true) + } + +} + +class MobAttributeDelegate(val name: String, val type: KClass, val defaultValue: T, persistent: Boolean = true) { + init { + val attrType = when { + type == Int::class || type == Long::class -> AttributeType.LONG + type == Boolean::class -> AttributeType.BOOLEAN + type == Double::class -> AttributeType.DOUBLE + type == String::class -> AttributeType.STRING + else -> throw IllegalArgumentException("Can only store primitive attributes, not: ${type.qualifiedName}") + } + + val definition = AttributeDefinition(defaultValue, if (persistent) AttributePersistence.PERSISTENT else AttributePersistence.TRANSIENT, attrType) + AttributeMap.define(name, definition) + } + + operator fun getValue(thisRef: Mob, property: KProperty<*>): T { + return thisRef.attributes[name]?.value as T? ?: defaultValue + } + + operator fun setValue(thisRef: Mob, property: KProperty<*>, value: T) { + val attr = when { + type == Double::class || type == Int::class || type == Long::class -> NumericalAttribute(value as Number) + type == Boolean::class -> BooleanAttribute(value as Boolean) + type == String::class -> StringAttribute(value as String, false) + else -> throw IllegalArgumentException("Can only store primitive attributes, not: ${type.qualifiedName}") + } + + thisRef.setAttribute(name, attr) + } +} diff --git a/game/plugin/api/src/org/apollo/game/plugins/api/player.kt b/game/plugin/api/src/org/apollo/game/plugins/api/player.kt new file mode 100644 index 000000000..98e25c928 --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugins/api/player.kt @@ -0,0 +1,28 @@ +package org.apollo.game.plugins.api + +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Skill +import org.apollo.game.model.entity.SkillSet + +val Mob.skills: SkillSet get() = skillSet +val SkillSet.attack: Skill get() = getSkill(Skill.ATTACK) +val SkillSet.defence: Skill get() = getSkill(Skill.DEFENCE) +val SkillSet.strength: Skill get() = getSkill(Skill.STRENGTH) +val SkillSet.hitpoints: Skill get() = getSkill(Skill.HITPOINTS) +val SkillSet.ranged: Skill get() = getSkill(Skill.RANGED) +val SkillSet.prayer: Skill get() = getSkill(Skill.PRAYER) +val SkillSet.magic: Skill get() = getSkill(Skill.MAGIC) +val SkillSet.cooking: Skill get() = getSkill(Skill.COOKING) +val SkillSet.woodcutting: Skill get() = getSkill(Skill.WOODCUTTING) +val SkillSet.fletching: Skill get() = getSkill(Skill.FLETCHING) +val SkillSet.fishing: Skill get() = getSkill(Skill.FISHING) +val SkillSet.firemaking: Skill get() = getSkill(Skill.FIREMAKING) +val SkillSet.crafting: Skill get() = getSkill(Skill.CRAFTING) +val SkillSet.smithing: Skill get() = getSkill(Skill.SMITHING) +val SkillSet.mining: Skill get() = getSkill(Skill.MINING) +val SkillSet.herblore: Skill get() = getSkill(Skill.HERBLORE) +val SkillSet.agility: Skill get() = getSkill(Skill.AGILITY) +val SkillSet.thieving: Skill get() = getSkill(Skill.THIEVING) +val SkillSet.slayer: Skill get() = getSkill(Skill.SLAYER) +val SkillSet.farming: Skill get() = getSkill(Skill.FARMING) +val SkillSet.runecraft: Skill get() = getSkill(Skill.RUNECRAFT) diff --git a/game/plugin/api/src/util.kt b/game/plugin/api/src/util.kt index 01264cbfe..dd80ef59b 100644 --- a/game/plugin/api/src/util.kt +++ b/game/plugin/api/src/util.kt @@ -1,9 +1,21 @@ -package org.apollo.game.plugin.api - -import java.util.Random +import java.lang.ref.WeakReference +import java.util.* +import kotlin.reflect.KProperty val RAND = Random() fun rand(bounds: Int): Int { return RAND.nextInt(bounds) -} \ No newline at end of file +} + +class WeakRefHolder(private var _value: WeakReference = WeakReference(null)) { + operator fun getValue(thisRef: Any?, property: KProperty<*>): T? { + return _value.get() + } + + operator fun setValue( + thisRef: Any?, property: KProperty<*>, value: T? + ) { + _value = WeakReference(value) + } +} diff --git a/game/plugin/entity/combat/build.gradle b/game/plugin/entity/combat/build.gradle new file mode 100644 index 000000000..fe9c71f44 --- /dev/null +++ b/game/plugin/entity/combat/build.gradle @@ -0,0 +1,11 @@ +plugin { + name = "combat" + description = "Extensible framework for combat." + authors = [ + "Gary Tierney " + ] + dependencies = [ + "util:lookup", + "api" + ] +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/combat.plugin.kts b/game/plugin/entity/combat/src/combat.plugin.kts new file mode 100644 index 000000000..7cbcf5753 --- /dev/null +++ b/game/plugin/entity/combat/src/combat.plugin.kts @@ -0,0 +1,37 @@ +import org.apollo.game.message.impl.NpcActionMessage +import org.apollo.game.model.event.impl.LogoutEvent + +/** + +on :message, :npc_action do |player, message| +target = $world.npc_repository.get message.index + +# unless target.attacking +# target_combat_state = get_combat_state target +# target_combat_state.target = player +# +# target.start_action CombatAction.new(target) +# end + +player_combat_state = player.get_combat_state +player_combat_state.target = target + +player.send HintIconMessage.for_npc(target.index) +player.walking_queue.clear +player.start_action CombatAction.new(player) +end + */ + +start { + +} + +on_player_event { LogoutEvent::class } + .then { + CombatStateManager.remove(it) + } + +on { NpcActionMessage::class } + .then { + CombatAction.start(this, it, it.world.npcRepository[index]) + } diff --git a/game/plugin/entity/combat/src/equipment/ammo/arrows.kt b/game/plugin/entity/combat/src/equipment/ammo/arrows.kt new file mode 100644 index 000000000..9ff35e8aa --- /dev/null +++ b/game/plugin/entity/combat/src/equipment/ammo/arrows.kt @@ -0,0 +1,71 @@ +object Arrows : AmmoType({ + fired from "shortbows" and "longbows" + + projectile { + startHeight = 41 + endHeight = 37 + delay = 41 + lifetime = 60 + pitch = 15 + } + + "bronze" { + requires level 1 + 30% dropChance + + graphics { + projectile = 10 + attack = 19 + } + } + + "iron" { + requires level 1 + 35% dropChance + + graphics { + projectile = 9 + attack = 18 + } + } + + "steel" { + requires level 5 + 40% dropChance + + graphics { + projectile = 11 + attack = 20 + } + } + + "mithril" { + requires level 20 + 45% dropChance + + graphics { + projectile = 12 + attack = 21 + } + } + + "adamant" { + requires level 30 + 50% dropChance + + graphics { + projectile = 13 + attack = 22 + } + } + + "rune" { + requires level 40 + 60% dropChance + + graphics { + projectile = 15 + attack = 24 + } + } +}) \ No newline at end of file diff --git a/game/plugin/entity/combat/src/equipment/weapons/melee/scimitars.plugin.kts b/game/plugin/entity/combat/src/equipment/weapons/melee/scimitars.plugin.kts new file mode 100644 index 000000000..2f1ca47e1 --- /dev/null +++ b/game/plugin/entity/combat/src/equipment/weapons/melee/scimitars.plugin.kts @@ -0,0 +1,65 @@ + + +Scimitar("Iron Scimitar") { + attackBonuses { + stab = 2 + slash = 10 + } + + meleeStrength = 9 +} + +Scimitar("Steel Scimitar") { + attackBonuses { + stab = 3 + slash = 15 + } + + meleeStrength = 14 +} + +// @todo - regexp support +//Scimitar("/(black|white) Scimitar/") { +// attackBonuses { +// stab = 4 +// slash = 19 +// } +// +// meleeStrength = 14 +//} + +Scimitar("Mithril Scimitar") { + attackBonuses { + stab = 5 + slash = 21 + } + + meleeStrength = 20 +} + +Scimitar("Adamant Scimitar") { + attackBonuses { + stab = 6 + slash = 29 + } + + meleeStrength = 28 +} + +Scimitar("Rune Scimitar") { + attackBonuses { + stab = 7 + slash = 45 + } + + meleeStrength = 44 +} + +Scimitar("Dragon Scimitar") { + attackBonuses { + stab = 8 + slash = 67 + } + + meleeStrength = 66 +} diff --git a/game/plugin/entity/combat/src/equipment/weapons/melee_classes.kt b/game/plugin/entity/combat/src/equipment/weapons/melee_classes.kt new file mode 100644 index 000000000..e0f61b8f5 --- /dev/null +++ b/game/plugin/entity/combat/src/equipment/weapons/melee_classes.kt @@ -0,0 +1,21 @@ +import AttackStyle.* +import org.apollo.game.model.Animation + +object Scimitar : MeleeWeaponClass({ + widgetId = 2423 + defaults { + attackSpeed = 4 + attackAnimation = Animation(390) + attackType = AttackType.Slash + } + + Accurate { button = 2 } + Aggressive { button = 3 } + AltAggressive { + button = 4 + attackType = AttackType.Stab + attackAnimation = Animation(391) + } + + Defensive { button = 5 } +}) diff --git a/game/plugin/entity/combat/src/equipment/weapons/ranged/bows.plugin.kts b/game/plugin/entity/combat/src/equipment/weapons/ranged/bows.plugin.kts new file mode 100644 index 000000000..33ee19a51 --- /dev/null +++ b/game/plugin/entity/combat/src/equipment/weapons/ranged/bows.plugin.kts @@ -0,0 +1,3 @@ +Bow("Shortbow") { + +} diff --git a/game/plugin/entity/combat/src/equipment/weapons/ranged_classes.kt b/game/plugin/entity/combat/src/equipment/weapons/ranged_classes.kt new file mode 100644 index 000000000..4bc815696 --- /dev/null +++ b/game/plugin/entity/combat/src/equipment/weapons/ranged_classes.kt @@ -0,0 +1,28 @@ +import AttackStyle.* +import AttackType.Ranged +import org.apollo.game.model.Animation + +object Bow : RangedWeaponClass(Arrows, { + widgetId = 1764 + + defaults { + attackType = Ranged + attackAnimation = Animation(426) + attackSpeed = 4 + attackRange = 7 + } + + Accurate { + button = 1772 + } + + Rapid { + button = 1770 + attackSpeed = 3 + + } + + LongRanged { + button = 1771 + } +}) diff --git a/game/plugin/entity/combat/src/framework/attack/attack.kt b/game/plugin/entity/combat/src/framework/attack/attack.kt new file mode 100644 index 000000000..0dceb956e --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/attack.kt @@ -0,0 +1,81 @@ +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Player + + +abstract class Attack( + val speed: Int, + val range: Int, + val type: AttackType, + val style: AttackStyle, + private val attackAnimation: Animation, + protected val requirements: MutableList +) { + /** + * Calculate the maximum damage this [Attack] can give out for the [source] [Mob]. + */ + abstract fun maxDamage(source: Mob): Int + + /** + * Check the requirements for this attack and execute it, making [AttackHitAttempt] rolls + * and depleting any resources used by [AttackRequirement]s. + */ + fun execute(source: Mob, target: Mob) { + val failingRequirement: AttackRequirementResult? = requirements.map({ it.check(source) }) + .firstOrNull({ it != AttackRequirementResult.Ok }) + + when (failingRequirement) { + is AttackRequirementResult.Failed -> { + (source as? Player)?.sendMessage(failingRequirement.message) + return + } + } + + val maxHitRoll = calculateBasicMaxRoll(RollType.Attack, source) + val maxDefenceRoll = calculateBasicMaxRoll(RollType.Defence, target) + + val accuracy = if (maxHitRoll > maxDefenceRoll) { + 1 - (maxDefenceRoll + 2) / (2 * (maxHitRoll + 1)) + } else { + maxHitRoll / (2 * (maxDefenceRoll + 1)) + } + + val maxDamageRoll = maxDamage(source) + val damageOutput = AttackOutput(type) + doAttack(source, target, damageOutput) + + requirements.forEach { it.apply(source) } + source.playAnimation(attackAnimation) + + damageOutput.rolls.forEach { + val hitRoll = Math.random() + val hitDamage = if (accuracy >= hitRoll) { + scale(0.0 to 0.99, 1.0 to (maxDamageRoll * it.modifier), it.roll) + } else { + 0 + } + + target.damage(hitDamage, if (hitDamage == 0) 0 else 1, false) + } + } + + protected abstract fun doAttack(source: Mob, target: Mob, output: AttackOutput) +} + +/** + * A basic attack that calculates maximum damage using the effective strength calculation. + * + * @todo - only use attacks for damage output / playing effects. tracking speed/range/type/style can happen elsewhere. + */ +abstract class BasicAttack( + speed: Int, + range: Int, + type: AttackType, + style: AttackStyle, + attackAnimation: Animation, + requirements: MutableList +) : Attack(speed, range, type, style, attackAnimation, requirements) { + override fun maxDamage(source: Mob): Int { + return calculateBasicMaxRoll(RollType.Damage, source) + } +} diff --git a/game/plugin/entity/combat/src/framework/attack/attack_damage.kt b/game/plugin/entity/combat/src/framework/attack/attack_damage.kt new file mode 100644 index 000000000..2e46f549c --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/attack_damage.kt @@ -0,0 +1,24 @@ +import org.apollo.game.model.entity.Projectile + +data class AttackHitAttempt(val delay: Int, val roll: Double, val modifier: Double) { + fun isImmediate(): Boolean { + return delay == 0 + } +} + +class AttackOutput(val type: AttackType) { + val rolls = mutableListOf() + + fun hit(modifier: Double = 1.0) { + rolls.add(AttackHitAttempt(0, Math.random(), modifier)) + } + + fun projectile(projectile: Projectile, modifier: Double = 1.0) { + val source = projectile.position + val target = projectile.destination + val projectileLifetime = projectile.delay + projectile.lifetime + source.getDistance(target) * 5 + val projectileTicks = Math.floor(projectileLifetime * 0.02587) + + rolls.add(AttackHitAttempt(projectileTicks.toInt(), Math.random(), modifier)) + } +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/attack_effect.kt b/game/plugin/entity/combat/src/framework/attack/attack_effect.kt new file mode 100644 index 000000000..c3b4c5222 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/attack_effect.kt @@ -0,0 +1,3 @@ +import org.apollo.game.model.Animation +import org.apollo.game.model.Graphic + diff --git a/game/plugin/entity/combat/src/framework/attack/attack_factory.kt b/game/plugin/entity/combat/src/framework/attack/attack_factory.kt new file mode 100644 index 000000000..69f0e8308 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/attack_factory.kt @@ -0,0 +1,6 @@ +import org.apollo.game.model.Animation + +//@todo - attack factory maybe not necessary when factoring details out of attack +interface AttackFactory { + fun createAttack(speed: Int, range: Int, type: AttackType, style: AttackStyle, animation: Animation): Attack +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/attack_requirement.kt b/game/plugin/entity/combat/src/framework/attack/attack_requirement.kt new file mode 100644 index 000000000..81beefc9b --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/attack_requirement.kt @@ -0,0 +1,65 @@ +import AttackRequirementResult.Failed +import AttackRequirementResult.Ok +import org.apollo.cache.def.EquipmentDefinition +import org.apollo.game.model.entity.EquipmentConstants +import org.apollo.game.model.entity.Mob + +sealed class AttackRequirementResult { + data class Failed(val message: String) : AttackRequirementResult() + object Ok : AttackRequirementResult() +} + +interface AttackRequirement { + /** + * Check if the [mob] initiating the [BasicAttack] meets the requirements. + */ + fun check(mob: Mob): AttackRequirementResult + + /** + * Apply this [AttackRequirement] to the [Mob] that initiated the doAttack. Optionally carrying out an action + * that removes any resources used up by the doAttack. + */ + fun apply(mob: Mob) +} + +class ItemRequirement(val itemId: Int, val amount: Int = 1) : AttackRequirement { + override fun check(mob: Mob): AttackRequirementResult { + if (mob.inventory.getAmount(itemId) < amount) { + return Failed("You don't have enough items") //@todo -message + } + + return Ok + } + + override fun apply(mob: Mob) { + mob.inventory.remove(itemId, amount) + } + +} + +class AmmoRequirement(val ammoType: AmmoType, val amount: Int) : AttackRequirement { + override fun check(mob: Mob): AttackRequirementResult { + val weaponItem = mob.equipment[EquipmentConstants.WEAPON] + val weaponReqLevel = EquipmentDefinition.lookup(weaponItem.id).rangedLevel + val ammoItem = mob.equipment[EquipmentConstants.ARROWS] + val ammo = ammoType.items[ammoItem?.id] + + if (ammoItem == null) { + return Failed("You have no ammo left in your quiver!") + } + + if (amount > ammoItem.amount) { + return Failed("You don't have enough ammo left in your quiver!") + } + + if (ammo == null || ammo.requiredLevel > weaponReqLevel) { + return Failed("You can't use this ammo with your current weapon.") + } + + return Ok + } + + override fun apply(mob: Mob) { + mob.equipment.removeSlot(EquipmentConstants.ARROWS, amount) + } +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/attack_type.kt b/game/plugin/entity/combat/src/framework/attack/attack_type.kt new file mode 100644 index 000000000..c886999a8 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/attack_type.kt @@ -0,0 +1,17 @@ +enum class AttackStyle(val attackBonus: Int = 0, val defenceBonus: Int = 0, val strengthBonus: Int = 0) { + Accurate(attackBonus = 3, strengthBonus = 3), + Aggressive(strengthBonus = 3), + Defensive(defenceBonus = 3), + Controlled(attackBonus = 1, strengthBonus = 1, defenceBonus = 1), + AltAggressive(strengthBonus = 3), + Rapid, + LongRanged(attackBonus = 1,strengthBonus = 1, defenceBonus = 3) +} + +enum class AttackType { + Stab, + Slash, + Crush, + Magic, + Ranged +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/attack_utils.kt b/game/plugin/entity/combat/src/framework/attack/attack_utils.kt new file mode 100644 index 000000000..c37411250 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/attack_utils.kt @@ -0,0 +1,74 @@ +import AttackType.Magic +import AttackType.Ranged +import CombatBonus.MeleeStrength +import CombatBonus.RangedStrength +import org.apollo.game.model.entity.Mob +import org.apollo.game.plugins.api.* + +enum class RollType { + Attack, + Defence, + Damage +} + +fun calculateBasicMaxRoll(rollType: RollType, mob: Mob, modifiers: List = emptyList()): Int { + val style = mob.combatState.attack.style + val attackType = mob.combatState.attack.type + + if (attackType == Magic) { + throw IllegalStateException("Basic roll calculator called for a magic attack. Unsupported") + } + + val styleBonus = when (rollType) { + RollType.Attack -> style.attackBonus + RollType.Defence -> style.defenceBonus + RollType.Damage -> { + if (style == AttackStyle.Accurate && attackType != Ranged) { + 0 + } else { + style.strengthBonus + } + } + } + + val baseSkill = when (rollType) { + RollType.Attack -> { + if (attackType == Ranged) { + mob.skills.ranged + } else { + mob.skills.attack + } + } + RollType.Defence -> mob.skills.defence + RollType.Damage -> { + if (attackType == Ranged) { + mob.skills.ranged + } else { + mob.skills.strength + } + } + } + + val equipmentBonuses = mob.combatState.bonuses + val equipmentBonus = when (rollType) { + RollType.Attack -> equipmentBonuses.attack[attackType] + RollType.Defence -> equipmentBonuses.defence[attackType] + RollType.Damage -> { + if (attackType == Ranged) { + equipmentBonuses[RangedStrength] + } else { + equipmentBonuses[MeleeStrength] + } + } + } + + val modifier = modifiers.reduce { a, b -> a + b } + val effectiveLevel = baseSkill.currentLevel * modifier + styleBonus + 8 + val maxRoll = if (rollType == RollType.Damage) { + 0.5 + effectiveLevel * (equipmentBonus + 64) / 640 + } else { + effectiveLevel * (equipmentBonus + 64) + } + + return maxRoll.toInt() +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/melee_attack.kt b/game/plugin/entity/combat/src/framework/attack/melee_attack.kt new file mode 100644 index 000000000..0efd38472 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/melee_attack.kt @@ -0,0 +1,21 @@ +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Mob + +object MeleeAttackFactory : AttackFactory { + override fun createAttack(speed: Int, range: Int, type: AttackType, style: AttackStyle, animation: Animation): Attack { + return MeleeAttack(speed, range, type, style, animation, mutableListOf()) + } +} + +class MeleeAttack( + speed: Int, + range: Int, + type: AttackType, + style: AttackStyle, + attackAnimation: Animation, + requirements: MutableList +) : BasicAttack(speed, range, type, style, attackAnimation, requirements) { + + override fun doAttack(source: Mob, target: Mob, output: AttackOutput) = output.hit() + +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/projectile_attack.kt b/game/plugin/entity/combat/src/framework/attack/projectile_attack.kt new file mode 100644 index 000000000..c05a1f80e --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/projectile_attack.kt @@ -0,0 +1,24 @@ +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.Mob + + +abstract class ProjectileAttack( + speed: Int, + range: Int, + type: AttackType, + style: AttackStyle, + attackAnimation: Animation, + requirements: MutableList +) : BasicAttack(speed, range, type, style, attackAnimation, requirements) { + + abstract fun projectile(mob: Mob): ProjectileTemplate + + override fun doAttack(source: Mob, target: Mob, output: AttackOutput) { + val projectileTemplate = projectile(source) + val projectile = projectileTemplate.factory.invoke(source.world, source.position, target) + + projectileTemplate.castGraphic?.let { source.playGraphic(it) } + source.world.spawn(projectile) + output.projectile(projectile) + } +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/projectile_template.kt b/game/plugin/entity/combat/src/framework/attack/projectile_template.kt new file mode 100644 index 000000000..976d34caa --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/projectile_template.kt @@ -0,0 +1,9 @@ +import org.apollo.game.model.Graphic + +//@todo - generalize for magic projectiles +data class ProjectileTemplate( + val factory: AmmoProjectileFactory, + val castGraphic: Graphic? = null, + val fumbleGraphic: Graphic? = null, + val hitGraphic: Graphic? = null +) \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/range_attack.kt b/game/plugin/entity/combat/src/framework/attack/range_attack.kt new file mode 100644 index 000000000..e453e457f --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/range_attack.kt @@ -0,0 +1,30 @@ +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.EquipmentConstants +import org.apollo.game.model.entity.Mob + +class RangeAttackFactory(val ammoType: AmmoType) : AttackFactory { + override fun createAttack(speed: Int, range: Int, type: AttackType, style: AttackStyle, animation: Animation): Attack { + return RangeAttack(speed, range, type, style, animation, ammoType) + } +} + +class RangeAttack( + speed: Int, + range: Int, + type: AttackType, + style: AttackStyle, + attackAnimation: Animation, + private val ammoType: AmmoType +) : ProjectileAttack(speed, range, type, style, attackAnimation, mutableListOf()) { + + init { + requirements.add(AmmoRequirement(ammoType, 1)) + } + + private fun ammo(mob: Mob): Ammo { + return mob.equipment[EquipmentConstants.ARROWS]?.let { ammoType.items[it.id] } + ?: throw IllegalStateException("Couldn't find ammo entry for equipped item") + } + + override fun projectile(mob: Mob) = ammo(mob).let { ProjectileTemplate(it.projectileFactory, it.attack) } +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/attack/special_attack.kt b/game/plugin/entity/combat/src/framework/attack/special_attack.kt new file mode 100644 index 000000000..b5891061f --- /dev/null +++ b/game/plugin/entity/combat/src/framework/attack/special_attack.kt @@ -0,0 +1 @@ +class SpecialAttack() \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/combat.kt b/game/plugin/entity/combat/src/framework/combat.kt new file mode 100644 index 000000000..019be7c1c --- /dev/null +++ b/game/plugin/entity/combat/src/framework/combat.kt @@ -0,0 +1,47 @@ +import org.apollo.game.action.Action +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Player +import org.apollo.net.message.Message + +class CombatAction(mob: T) : Action(0, true, mob) { + companion object { + + /** + * Starts a [CombatAction] for the specified [Player], terminating the [Message] that triggered. + */ + fun start(message: Message, player: Player, target: Mob) { + player.combatState.target = target + player.interactingMob = target + player.turnTo(target.position) + player.startAction(CombatAction(player)) + message.terminate() + } + } + + override fun execute() { + val state = mob.combatState + val target = state.target + val attack = state.attack + + if (target == null) { + stop() + return + } + + if (!state.inRange()) { + // @todo - chase 'til in range + return + } else { + // @todo - chasing will prevent running closer than needed + mob.walkingQueue.clear() + } + + if (!state.canAttack()) { + // @todo - idle - waiting to attack, do block animation + return + } + + attack.execute(mob, target) + mob.combatAttackTick = mob.world.tick() + } +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/combat_bonuses.kt b/game/plugin/entity/combat/src/framework/combat_bonuses.kt new file mode 100644 index 000000000..b5d81f100 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/combat_bonuses.kt @@ -0,0 +1,74 @@ +enum class CombatBonus { + MeleeStrength, + RangedStrength, + Prayer +} + +data class AttackBonuses(private val bonuses: Map) { + companion object { + fun default() = AttackBonusesBuilder().build() + } + + operator fun get(key: AttackType): Int = bonuses[key]!! +} + +data class CombatBonuses( + val attack: AttackBonuses, + val defence: AttackBonuses, + private val combatBonuses: Map +) { + companion object { + fun default() = CombatBonusesBuilder().build() + } + + operator fun get(key: CombatBonus): Int = combatBonuses[key]!! +} + +class CombatBonusesBuilder { + var meleeStrength = 0 + var rangedStrength = 0 + var prayer = 0 + var attackBonuses = AttackBonuses.default() + var defenceBonuses = AttackBonuses.default() + + fun attack(configurer: AttackBonusesBuilder.() -> Unit) { + val builder = AttackBonusesBuilder() + builder.configurer() + + attackBonuses = builder.build() + } + + fun defence(configurer: AttackBonusesBuilder.() -> Unit) { + val builder = AttackBonusesBuilder() + builder.configurer() + + defenceBonuses = builder.build() + } + + fun build(): CombatBonuses { + return CombatBonuses(attackBonuses, defenceBonuses, mapOf( + CombatBonus.MeleeStrength to meleeStrength, + CombatBonus.RangedStrength to rangedStrength, + CombatBonus.Prayer to prayer + )) + } +} + +class AttackBonusesBuilder( + var stab: Int = 0, + var slash: Int = 0, + var crush: Int = 0, + var magic: Int = 0, + var range: Int = 0 +) { + + fun build(): AttackBonuses { + return AttackBonuses(mapOf( + AttackType.Stab to stab, + AttackType.Slash to slash, + AttackType.Crush to crush, + AttackType.Magic to magic, + AttackType.Ranged to range + )) + } +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/combat_state.kt b/game/plugin/entity/combat/src/framework/combat_state.kt new file mode 100644 index 000000000..c5ce78eef --- /dev/null +++ b/game/plugin/entity/combat/src/framework/combat_state.kt @@ -0,0 +1,59 @@ +import MobAttributeDelegators.attribute +import org.apollo.game.model.entity.EntityType +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.Player +import java.util.* + +//@todo - CombatState interface and NpcCombatState/PlayerCombatState to better drive +// behaviours +object CombatStateManager { + private val states = WeakHashMap() + + fun stateFor(mob: Mob): CombatState { + return states.computeIfAbsent(mob, { + val combatStyle = when (mob) { + is Player -> mob.weapon.weaponClass.styles[mob.combatStyle] + is Npc -> Weapons[null].weaponClass.styles[0] // @todo + else -> throw IllegalStateException("Invalid type: ${mob.javaClass.name}") + } + + CombatState(it, combatStyle.attack) + }) + } + + fun remove(it: Mob) { + states.remove(it) + } +} + +var Mob.combatStyle: Int by attribute("combat_style", 0) +var Mob.combatAttackTick: Long by attribute("combat_attack_tick", 0) + +class CombatState(private val mob: Mob, var attack: Attack) { + var target: Mob? by WeakRefHolder() + var bonuses = CombatBonuses.default() + + fun ticksSinceAttack(): Long { + return mob.world.tick() - mob.combatAttackTick + } + + fun inRange(): Boolean { + val target = this.target ?: return false + val distance = mob.position.getDistance(target.position) + val objectType = when (attack.type) { + AttackType.Ranged -> EntityType.PROJECTILE + else -> EntityType.NPC + } + + return distance <= attack.range /* && mob.world.collisionManager.raycast(mob.position, target.position, objectType) */ + } + + fun canAttack(): Boolean { + return ticksSinceAttack() >= attack.speed + } +} + +val Mob.combatState: CombatState + get() = CombatStateManager.stateFor(this) + diff --git a/game/plugin/entity/combat/src/framework/equipment/ammo.kt b/game/plugin/entity/combat/src/framework/equipment/ammo.kt new file mode 100644 index 000000000..caa9db18d --- /dev/null +++ b/game/plugin/entity/combat/src/framework/equipment/ammo.kt @@ -0,0 +1,270 @@ +import org.apollo.game.model.Graphic +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Projectile + +open class AmmoType(configurer: AmmoTypeBuilder.() -> Unit) { + val items : Map + + init { + val ammo = AmmoTypeBuilder(this.javaClass.simpleName) + .also(configurer) + .build() + + items = HashMap(ammo) + } +} + +data class AmmoEnchantment( + val graphic: Graphic, + val effect: AmmoEnchantmentEffect, + val chanceSupplier: AmmoEnchantmentChanceSupplier +) + +data class Ammo( + val name: String, + val requiredLevel: Int, + val dropChance: Double, + val projectileFactory: AmmoProjectileFactory, + val attack: Graphic? = null, + val enchantment: AmmoEnchantment? = null +) + +typealias AmmoEnchantmentEffect = Mob.() -> Unit +typealias AmmoEnchantmentChanceSupplier = Mob.() -> Double +typealias AmmoProjectileFactory = (World, Position, Mob) -> Projectile + +/** + * A [DslMarker] for the ammo DSL. + */ +@DslMarker annotation class AmmoDslMarker + +/** + * A builder for the graphics related to firing ammo. + */ +@AmmoDslMarker +class AmmoGraphicsBuilder { + /** + * The graphic used in the projectile fired. + */ + var projectile: Int? = null + + /** + * The graphic used when the attacker fires the shot. + */ + var attack: Int? = null + + /** + * The graphic used when a [Mob] is doAttack with an enchanted ammo effect. + */ + var enchanted: Int? = null + + operator fun invoke(builder: AmmoGraphicsBuilder.() -> Unit) = builder(this) +} + +@AmmoDslMarker +class AmmoDropChance + +/** + * A DSL builder for an ammo's ranged level requirements. + */ +@AmmoDslMarker +class AmmoRequirementBuilder { + var level: Int = 1 + + /** + * Set the ranged level required to use this ammo. + */ + infix fun level(requirement: Int): AmmoRequirementBuilder { + this.level = requirement + return this + } +} + +@AmmoDslMarker +class AmmoBuilder(val name: String) { + /** + * The chance that ammo of this type will be dropped rather than broken. + */ + var dropChanceValue: Double = 1.0 + + /** + * value to hold a dummy [AmmoDropChance] field that can be used by the overriden `%` operator. + */ + val dropChance = AmmoDropChance() + + /** + * `%` operator overload on [Int] for a fluent way to define an [Ammo]'s chance of dropping instead of breaking. + */ + operator fun Int.rem(placeholder: AmmoDropChance) { + dropChanceValue = this / 100.0 + } + + /** + * The graphics played when this ammo is used. + */ + val graphics = AmmoGraphicsBuilder() + + /** + * An optional enchantment associated with this ammo. + */ + val enchantment = AmmoEnchantmentBuilder() + + /** + * The level requirements needed to use this ammo. + */ + val requires = AmmoRequirementBuilder() +} + +/** + * A builder for effects applied to [Mob]'s doAttack with ammo that can be chanted. + */ +@AmmoDslMarker +class AmmoEnchantmentBuilder { + var effect: AmmoEnchantmentEffect? = null + var chance: AmmoEnchantmentChanceSupplier? = null + + /** + * Set the effect that will be applied to a [Mob] whenever this enchantment + * is succesfully applied. + */ + infix fun effect(effect: AmmoEnchantmentEffect): AmmoEnchantmentBuilder { + this.effect = effect + return this + } + + /** + * Set the function used to calculate the chance of an enchantment triggering + * on a projectile fired by a given [Mob]. + */ + infix fun chance(chanceSupplier: AmmoEnchantmentChanceSupplier): AmmoEnchantmentBuilder { + this.chance = chanceSupplier + return this + } +} + +/** + * A builder for the weapon classes an [AmmoType] can be used with. + */ +@AmmoDslMarker +class AmmoTypeUseabilityBuilder { + /** + * A list of weapon class names that the [AmmoType] can be used with. + */ + val weaponClasses = mutableListOf() + + /** + * Include [Ammo] under the current ammo type as useable by the given `weaponClass`. + */ + infix fun from(weaponClass: String): AmmoTypeUseabilityBuilder = and(weaponClass) + + /** + * Include [Ammo] under the current ammo type as useable by the given `weaponClass`. + */ + infix fun and(weaponClass: String): AmmoTypeUseabilityBuilder { + weaponClasses.add(weaponClass) + return this + } +} + +/** + * A builder DSL for an [AmmoProjectileFactory]. + */ +@AmmoDslMarker +class AmmoProjectileFactoryBuilder { + + var startHeight: Int = 40 + var endHeight: Int = 35 + var delay: Int? = null + var lifetime: Int? = null + var pitch: Int? = null + + operator fun invoke(builder: AmmoProjectileFactoryBuilder.() -> Unit) = builder(this) + + /** + * Build an [AmmoProjectileFactory] for the [Ammo] variant with the given graphic ID. + */ + fun build(variant: Int): AmmoProjectileFactory = { world, source, target -> + Projectile.ProjectileBuilder(world) + .startHeight(startHeight) + .endHeight(endHeight) + .delay(delay!!) + .lifetime(lifetime!!) + .pitch(pitch!!) + .graphic(variant) + .source(source) + .target(target) + .build() + } +} + +/** + * A builder for a collection of [Ammo] of the same type. + */ +@AmmoDslMarker +class AmmoTypeBuilder(val name: String) { + /** + * The constraints on what weapon classes this ammo type can be fired from. + */ + val fired = AmmoTypeUseabilityBuilder() + + /** + * The base projectile factory for projectiles of this ammo type. + */ + val projectile = AmmoProjectileFactoryBuilder() + + /** + * The variants of ammo that belong to this ammo type. + */ + val variants = mutableListOf() + + /** + * String invocation overload that creates a new ammo variant + * given an [AmmoBuilder] + */ + operator fun String.invoke(builder: AmmoBuilder.() -> Unit) { + val variant = AmmoBuilder(this) + builder(variant) + + variants.add(variant) + } + + /** + * Build a list of ItemID -> [Ammo] pairs based on the variants in this [AmmoTypeBuilder]. + */ + fun build(): Map { + val ammoTypeSingular = name.removeSuffix("s") + val ammoMap = mutableMapOf() + + variants.forEach { + val requiredLevel = it.requires.level + val dropChance = it.dropChanceValue + + val projectileGraphicId = it.graphics.projectile ?: throw RuntimeException("Every ammo requires a projectile id") + val projectileFactory = projectile.build(projectileGraphicId) + val attack = it.graphics.attack?.let({ Graphic(it, 0, 100) }) + + val ammoName = "${it.name} $ammoTypeSingular" + val ammo = Ammo(name, requiredLevel, dropChance, projectileFactory, attack) + val ammoId = lookup_item(ammoName)?.id ?: throw RuntimeException("Unable to find ammo named $ammoName") + + ammoMap[ammoId] = ammo + + val enchantmentGraphic = it.graphics.enchanted?.let(::Graphic) + val enchantmentEffect = it.enchantment.effect + val enchantmentChanceSupplier = it.enchantment.chance + + if (enchantmentGraphic != null && enchantmentEffect != null && enchantmentChanceSupplier != null) { + val enchantment = AmmoEnchantment(enchantmentGraphic, enchantmentEffect, enchantmentChanceSupplier) + val enchantedAmmoName = "$ammoName (e)" + val enchantedAmmo = Ammo(name, requiredLevel, dropChance, projectileFactory, attack, enchantment) + val enchantedAmmoId = lookup_item(enchantedAmmoName)?.id ?: throw RuntimeException("Unable to find ammo named $enchantedAmmoName") + + ammoMap[enchantedAmmoId] = enchantedAmmo + } + } + + return ammoMap + } +} diff --git a/game/plugin/entity/combat/src/framework/equipment/weapon.kt b/game/plugin/entity/combat/src/framework/equipment/weapon.kt new file mode 100644 index 000000000..97eb84499 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/equipment/weapon.kt @@ -0,0 +1,79 @@ + +import AttackStyle.* +import AttackType.Crush +import Weapons.createWeapon +import org.apollo.game.model.Animation + +data class Weapon(val weaponClass: WeaponClass, val bonuses: CombatBonuses, val specialAttack: SpecialAttack? = null) + +operator fun WeaponClass.invoke(name: String, configurer: WeaponBuilder.() -> Unit) { + val weaponItem = lookup_item(name) ?: throw IllegalArgumentException("Invalid weapon name: ${name}") + Weapons.weaponMap[weaponItem.id] = createWeapon(this, configurer) +} + +object Weapons { + internal val weaponMap = mutableMapOf() + + operator fun get(itemId: Int?): Weapon { + return weaponMap[itemId] ?: defaultWeapon + } + + internal fun createWeapon(weaponClass: WeaponClass, configurer: WeaponBuilder.() -> Unit = {}): Weapon { + return WeaponBuilder(weaponClass) + .also(configurer) + .build() + } +} + +object Unarmed : MeleeWeaponClass({ + widgetId = 5855 + + defaults { + attackSpeed = 4 + attackType = Crush + attackAnimation = Animation(422) + } + + Accurate { + button = 5860 + } + + Aggressive { + button = 5862 + attackAnimation = Animation(423) + } + + Defensive { + button = 5861 + } +}) + +private val defaultWeapon = Weapons.createWeapon(Unarmed) + +class WeaponBuilder(private val weaponClass: WeaponClass) { + private val combatBonusesBuilder = CombatBonusesBuilder() + + var specialAttack: SpecialAttack? = null + var meleeStrength: Int + get() = combatBonusesBuilder.meleeStrength + set(value) { + combatBonusesBuilder.meleeStrength = value + } + + var rangedStrength: Int + get() = combatBonusesBuilder.rangedStrength + set(value) { + combatBonusesBuilder.rangedStrength = value + } + + var prayer: Int + get() = combatBonusesBuilder.prayer + set(value) { + combatBonusesBuilder.prayer = value + } + + fun attackBonuses(configurer: AttackBonusesBuilder.() -> Unit) = this.combatBonusesBuilder.attack(configurer) + fun defenceBonuses(configurer: AttackBonusesBuilder.() -> Unit) = this.combatBonusesBuilder.defence(configurer) + + fun build() = Weapon(weaponClass, combatBonusesBuilder.build(), specialAttack) +} diff --git a/game/plugin/entity/combat/src/framework/equipment/weapon_class.kt b/game/plugin/entity/combat/src/framework/equipment/weapon_class.kt new file mode 100644 index 000000000..06393d673 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/equipment/weapon_class.kt @@ -0,0 +1,95 @@ +import org.apollo.game.model.Animation + +data class SpecialBar(val button: Int, val configId: Int) +data class WeaponClassDetails(val widget: Int, val specialBar: SpecialBar?, val styles: List) +data class WeaponClassStyle(val button: Int, val attackStyle: AttackStyle, val attack: Attack, val blockAnimation: Animation?) + +typealias WeaponClassConfigurer = WeaponClassDetailsBuilder.() -> Unit + +open class WeaponClass(attackFactory: AttackFactory, detailsBuilder: WeaponClassConfigurer) { + val widget: Int + val specialBar: SpecialBar? + val styles: Array + + init { + val details = WeaponClassDetailsBuilder(attackFactory) + .also(detailsBuilder) + .build() + + widget = details.widget + specialBar = details.specialBar + styles = details.styles.toTypedArray() + } +} + +class WeaponClassDetailsBuilder(private val attackFactory: AttackFactory) { + private var configureStyleDefaults: WeaponClassStyleBuilder.() -> Unit = {} + private var specialBar: SpecialBar? = null + private val styles = mutableListOf() + + var widgetId: Int? = null + + fun defaults(configurer: WeaponClassStyleBuilder.() -> Unit) { + configureStyleDefaults = configurer + } + + fun specialBar(configurer: SpecialBarBuilder.() -> Unit) { + specialBar = SpecialBarBuilder().also(configurer).build() + } + + operator fun AttackStyle.invoke(configurer: WeaponClassStyleBuilder.() -> Unit) { + val styleBuilder = WeaponClassStyleBuilder(this) + .also(configureStyleDefaults) + .also(configurer) + + styles.add(styleBuilder.build(attackFactory)) + } + + fun build(): WeaponClassDetails { + return WeaponClassDetails( + widgetId ?: throw IllegalStateException("Weapon class widget id is required"), + specialBar, + styles + ) + } +} + +class WeaponClassStyleBuilder(val attackStyle: AttackStyle) { + var button: Int? = null + var configId: Int? = null + var attackRange: Int? = 1 + var attackSpeed: Int? = null + var attackType: AttackType? = null + var attackAnimation: Animation? = null + var blockAnimation: Animation? = null + var attack: Attack? = null + + fun build(attackFactory: AttackFactory): WeaponClassStyle { + val attack = this.attack ?: attackFactory.createAttack( + attackSpeed ?: throw IllegalStateException("BasicAttack speed is required"), + attackRange ?: throw IllegalStateException("BasicAttack range is required"), + attackType ?: throw IllegalStateException("BasicAttack type is required"), + attackStyle, + attackAnimation ?: throw IllegalStateException("BasicAttack animation is required") + ) + + return WeaponClassStyle( + button ?: throw IllegalStateException("Combat style button is required"), + attackStyle, + attack, + blockAnimation + ) + } +} + +class SpecialBarBuilder { + var button: Int? = null + var configId: Int? = null + + fun build(): SpecialBar { + return SpecialBar( + button ?: throw IllegalStateException("No button configured for special bar"), + configId ?: throw IllegalStateException("No config id configured for special bar") + ) + } +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/equipment/weapons/melee.kt b/game/plugin/entity/combat/src/framework/equipment/weapons/melee.kt new file mode 100644 index 000000000..35f50e39f --- /dev/null +++ b/game/plugin/entity/combat/src/framework/equipment/weapons/melee.kt @@ -0,0 +1,2 @@ +abstract class MeleeWeaponClass(detailsBuilder: WeaponClassConfigurer) : + WeaponClass(MeleeAttackFactory, detailsBuilder) \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/equipment/weapons/ranged.kt b/game/plugin/entity/combat/src/framework/equipment/weapons/ranged.kt new file mode 100644 index 000000000..96a45669c --- /dev/null +++ b/game/plugin/entity/combat/src/framework/equipment/weapons/ranged.kt @@ -0,0 +1,2 @@ +abstract class RangedWeaponClass(ammoType: AmmoType, detailsBuilder: WeaponClassConfigurer) + : WeaponClass(RangeAttackFactory(ammoType), detailsBuilder) \ No newline at end of file diff --git a/game/plugin/entity/combat/src/framework/utils.kt b/game/plugin/entity/combat/src/framework/utils.kt new file mode 100644 index 000000000..0c8d08d31 --- /dev/null +++ b/game/plugin/entity/combat/src/framework/utils.kt @@ -0,0 +1,14 @@ +import org.apollo.game.model.entity.EquipmentConstants +import org.apollo.game.model.entity.Mob + +val Mob.weapon: Weapon + get() = Weapons[this.equipment[EquipmentConstants.WEAPON]?.id] + +fun scale(oldScale: Pair, newScale: Pair, value: Double): Int { + val oldMin = oldScale.first + val oldMax = oldScale.second + val newMin = newScale.first + val newMax = newScale.second + + return Math.round((newMax - newMin) * (value - oldMin) / (oldMax - oldMin) + newMin).toInt(); +} \ No newline at end of file diff --git a/game/plugin/entity/combat/src/todo.txt b/game/plugin/entity/combat/src/todo.txt new file mode 100644 index 000000000..115faf127 --- /dev/null +++ b/game/plugin/entity/combat/src/todo.txt @@ -0,0 +1,18 @@ +[x] combat timing +[x] combat tab ui +[x] attack requirements +[x] melee attacks +[x] projectile attacks +[x] max hit calculation +[x] applying damage rolls +[x] attack vs defence (accuracy) checks for ranged/melee +[ ] weapon posture animations +[ ] playing block animations +[ ] special attacks +[ ] bonus calculations / attack modifiers + buffs +[ ] death checks / loot +[ ] persistent attack timer +[ ] chasing +[ ] equipment tab ui +[ ] magic combat +[ ] blocking logout diff --git a/game/plugin/entity/combat/src/ui/combat_tab.plugin.kts b/game/plugin/entity/combat/src/ui/combat_tab.plugin.kts new file mode 100644 index 000000000..505eca66d --- /dev/null +++ b/game/plugin/entity/combat/src/ui/combat_tab.plugin.kts @@ -0,0 +1,47 @@ + +import org.apollo.game.message.impl.* +import org.apollo.game.model.entity.EquipmentConstants +import org.apollo.game.model.entity.Player +import org.apollo.game.model.event.impl.LoginEvent +import org.apollo.game.model.inv.SynchronizationInventoryListener + +on_player_event { LoginEvent::class }.then { updateCombatTab(player) } + +on { ItemOptionMessage::class } + .where { interfaceId == SynchronizationInventoryListener.INVENTORY_ID && option == 2 } + .then { updateCombatTab(it) } + +on { ItemActionMessage::class } + .where { interfaceId == SynchronizationInventoryListener.EQUIPMENT_ID && slot == EquipmentConstants.WEAPON } + .then { updateCombatTab(it) } + +on { ButtonMessage::class } + .then { + val weapon = it.weapon + val weaponClass = weapon.weaponClass + val newCombatStyle = weaponClass.styles.firstOrNull { it.button == widgetId } ?: return@then + + it.combatStyle = weaponClass.styles.indexOf(newCombatStyle) + it.combatState.attack = newCombatStyle.attack + } + +fun updateCombatTab(player: Player) { + val weaponItem = player.equipment[EquipmentConstants.WEAPON] + val weaponName = weaponItem?.definition?.name ?: "Unarmed" + val weapon = Weapons[weaponItem?.id] + val weaponClass = weapon.weaponClass + var widget = weaponClass.widget + + player.send(SwitchTabInterfaceMessage(0, widget)) + + if (weaponItem != null) { + player.send(SetWidgetItemModelMessage(++widget, weaponItem.id, 200)) + } +/* + if weapon_class.special_bar? + player.send SetWidgetVisibilityMessage.new(weapon_class.special_bar_config, weapon.special_attack?) + end +*/ + player.send(SetWidgetTextMessage(widget + 2, weaponName)) + player.send(ConfigMessage(43, player.combatStyle)) //@todo - combat style offset +} diff --git a/game/plugin/entity/combat/test/AmmoDslTests.kt b/game/plugin/entity/combat/test/AmmoDslTests.kt new file mode 100644 index 000000000..2dd405ac3 --- /dev/null +++ b/game/plugin/entity/combat/test/AmmoDslTests.kt @@ -0,0 +1,56 @@ + +import org.apollo.cache.def.ItemDefinition +import org.junit.Before +import org.junit.Test + +// @fixme +class AmmoDslTests { + + private val TEST_AMMO_ID : Int = 0 + + @Before + fun setupTestAmmo() { + val testItem = ItemDefinition(TEST_AMMO_ID) + testItem.name = "bronze arrow" + + ItemDefinition.init(arrayOf(testItem)) + } + + @Test + fun `Ammo variants should inherit projectile parameters from their ammo type`() { +// +// val ammo = AMMO[TEST_AMMO_ID]!! +// val world = World() +// val target = Player(world, PlayerCredentials("fake", "fake", 1, 1, "fake"), Position(1, 1)) +// val source = Position(1, 1) +// val ammoProjectile = ammo.projectileFactory(World(), source, target) +// +// assertEquals(10, ammoProjectile.startHeight) +// assertEquals(20, ammoProjectile.endHeight) +// assertEquals(30, ammoProjectile.delay) +// assertEquals(40, ammoProjectile.lifetime) +// assertEquals(60, ammoProjectile.pitch) +// assertEquals(10, ammoProjectile.graphic) + } + + @Test + fun `Ammo variants should set their level requirements correctly`() { +// val ammo = AMMO[TEST_AMMO_ID]!! +// +// assertEquals(1, ammo.requiredLevel) + } + + @Test + fun `Ammo variants should set their drop chance correctly`() { +// val ammo = AMMO[TEST_AMMO_ID]!! +// +// assertEquals(ammo.dropChance, 0.2, 0.0) + } + + @Test + fun `Ammo variants should set their graphics correctly`() { +// val ammo = AMMO[TEST_AMMO_ID]!! +// +// assertEquals(20, ammo.attack?.id) + } +} \ No newline at end of file diff --git a/game/src/main/java/org/apollo/game/model/World.java b/game/src/main/java/org/apollo/game/model/World.java index 991a41fc6..3473eb8cf 100644 --- a/game/src/main/java/org/apollo/game/model/World.java +++ b/game/src/main/java/org/apollo/game/model/World.java @@ -66,6 +66,11 @@ public enum RegistrationStatus { } + /** + * A counter for the number of ticks ran. + */ + private long tickCounter = 0; + /** * The logger for this class. */ @@ -208,6 +213,15 @@ public int getReleaseNumber() { return releaseNumber; } + /** + * Get the current value of the {@link #tickCounter} (the number of ticks since the game started). + * + * @return The current value of the tick counter; + */ + public long tick() { + return tickCounter; + } + /** * Initialises the world by loading definitions from the specified file * system. @@ -277,6 +291,7 @@ public void pulse() { unregisterNpcs(); registerNpcs(); scheduler.pulse(); + tickCounter++; } /** diff --git a/game/src/main/java/org/apollo/game/model/area/Region.java b/game/src/main/java/org/apollo/game/model/area/Region.java index df7a9a170..0f2693366 100644 --- a/game/src/main/java/org/apollo/game/model/area/Region.java +++ b/game/src/main/java/org/apollo/game/model/area/Region.java @@ -400,7 +400,7 @@ private void checkPosition(Position position) { */ private void record(T entity, EntityUpdateType update) { UpdateOperation operation = entity.toUpdateOperation(this, update); - RegionUpdateMessage message = operation.toMessage(), inverse = operation.inverse(); + RegionUpdateMessage message = operation.toMessage(); int height = entity.getPosition().getHeight(); Set updates = this.updates.get(height); @@ -411,12 +411,12 @@ private void record(T entity, EntityUpdateT if (update == EntityUpdateType.REMOVE) { removedObjects.get(height).add(message); } else { // TODO should this really be possible? - removedObjects.get(height).remove(inverse); + removedObjects.get(height).remove(operation.inverse()); } } if (update == EntityUpdateType.REMOVE && !type.isTransient()) { - updates.remove(inverse); + updates.remove(operation.inverse()); } updates.add(message); diff --git a/game/src/main/java/org/apollo/game/model/area/collision/CollisionManager.java b/game/src/main/java/org/apollo/game/model/area/collision/CollisionManager.java index 4b54b36ac..10d02dabe 100644 --- a/game/src/main/java/org/apollo/game/model/area/collision/CollisionManager.java +++ b/game/src/main/java/org/apollo/game/model/area/collision/CollisionManager.java @@ -145,14 +145,15 @@ public void apply(CollisionUpdate update) { } /** - * Casts a ray into the world to check for impenetrable objects from the given {@code start} position to the + * Casts a ray into the world to check for impenetrable objects from the given {@code start} position to the * {@code end} position using Bresenham's line algorithm. * * @param start The start position of the ray. * @param end The end position of the ray. + * @param objectType * @return {@code true} if an impenetrable object was hit, {@code false} otherwise. */ - public boolean raycast(Position start, Position end) { + public boolean raycast(Position start, Position end, EntityType objectType) { Preconditions.checkArgument(start.getHeight() == end.getHeight(), "Positions must be on the same height"); if (start.equals(end)) { @@ -221,7 +222,7 @@ public boolean raycast(Position start, Position end) { Direction direction = Direction.fromDeltas(currX - lastX, currY - lastY); Position last = new Position(lastX, lastY, start.getHeight()); - if (!traversable(last, EntityType.PROJECTILE, direction)) { + if (!traversable(last, objectType, direction)) { return false; } diff --git a/game/src/main/java/org/apollo/game/model/entity/AnimationMap.java b/game/src/main/java/org/apollo/game/model/entity/AnimationMap.java new file mode 100644 index 000000000..a50662e84 --- /dev/null +++ b/game/src/main/java/org/apollo/game/model/entity/AnimationMap.java @@ -0,0 +1,194 @@ +package org.apollo.game.model.entity; + +/** + * A map of animations for {@link Player}s. Contains animations such as standing, turning, running, etc. + * + * @author Steve Soltys + */ +public class AnimationMap { + /** + * The default animation set for {@link Player}s. + */ + public static final AnimationMap DEFAULT_ANIMATION_SET = new AnimationMap(0x328, 0x337, 0x333, 0x334, 0x335, 0x336, 0x338); + + /** + * The animation for standing in place. + */ + private int stand; + + /** + * The animation for turning while idle. + */ + private int idleTurn; + + /** + * The animation for walking. + */ + private int walking; + + /** + * The animation for turning 180 degrees. + */ + private int turnAround; + + /** + * The animation for turning 90 degrees right. + */ + private int turnRight; + + /** + * The animation for turning 90 degrees left. + */ + private int turnLeft; + + /** + * The animation for running. + */ + private int running; + + public AnimationMap(int stand, int idleTurn, int walking, int turnAround, int turnRight, int turnLeft, int running) { + this.stand = stand; + this.idleTurn = idleTurn; + this.walking = walking; + this.turnAround = turnAround; + this.turnRight = turnRight; + this.turnLeft = turnLeft; + this.running = running; + } + + /** + * Duplicates this animation set. + * + * @return the duplicated animation set. + */ + @Override + public AnimationMap clone() { + return new AnimationMap(stand, idleTurn, walking, turnAround, turnRight, turnLeft, running); + } + + /** + * Gets the animation for standing in place. + * + * @return the animation. + */ + public int getStand() { + return stand; + } + + /** + * Sets the animation for standing in place. + * + * @param stand the animation. + */ + public void setStand(int stand) { + this.stand = stand; + } + + /** + * Gets the animation for turning while idle. + * + * @return the animation. + */ + public int getIdleTurn() { + return idleTurn; + } + + /** + * Sets the animation for standing in place. + * + * @param idleTurn the animation. + */ + public void setIdleTurn(int idleTurn) { + this.idleTurn = idleTurn; + } + + /** + * Gets the animation for walking. + * + * @return the animation. + */ + public int getWalking() { + return walking; + } + + /** + * Sets the animation for walking. + * + * @param walking the animation. + */ + public void setWalking(int walking) { + this.walking = walking; + } + + /** + * Gets the animation for turning 180 degrees. + * + * @return the animation. + */ + public int getTurnAround() { + return turnAround; + } + + /** + * Sets the animation for turning 180 degrees. + * + * @param turnAround the animation. + */ + public void setTurnAround(int turnAround) { + this.turnAround = turnAround; + } + + /** + * Gets the animation for turning 90 degrees right. + * + * @return the animation. + */ + public int getTurnRight() { + return turnRight; + } + + /** + * Sets the animation for turning 90 degrees right. + * + * @param turnRight the animation. + */ + public void setTurnRight(int turnRight) { + this.turnRight = turnRight; + } + + /** + * Gets the animation for turning 90 degrees left. + * + * @return the animation. + */ + public int getTurnLeft() { + return turnLeft; + } + + /** + * Sets the animation for turning 90 degrees left. + * + * @param turnLeft the animation. + */ + public void setTurnLeft(int turnLeft) { + this.turnLeft = turnLeft; + } + + /** + * Gets the animation for running. + * + * @return the animation. + */ + public int getRunning() { + return running; + } + + /** + * Sets the animation for running. + * + * @param running the animation. + */ + public void setRunning(int running) { + this.running = running; + } +} diff --git a/game/src/main/java/org/apollo/game/model/entity/Player.java b/game/src/main/java/org/apollo/game/model/entity/Player.java index 76c06db95..35eacba4d 100644 --- a/game/src/main/java/org/apollo/game/model/entity/Player.java +++ b/game/src/main/java/org/apollo/game/model/entity/Player.java @@ -64,6 +64,11 @@ */ public final class Player extends Mob { + /** + * The {@code Mob}s current set of animations used in appearance updates. + */ + private AnimationMap animations = AnimationMap.DEFAULT_ANIMATION_SET; + /** * The default viewing distance, in tiles. */ @@ -317,6 +322,15 @@ public boolean friendsWith(String username) { return friends.contains(username.toLowerCase()); } + /** + * Gets the current set of animations used for character movement. + * + * @return The animation set. + */ + public final AnimationMap getAnimations() { + return animations; + } + /** * Gets the player's appearance. * @@ -797,6 +811,15 @@ public void sendUserLists() { } } + /** + * Sets the set of animations to use in {@code Mob} appearance updates. + * + * @param animations The set of animations to use. + */ + public final void setAnimations(AnimationMap animations) { + this.animations = animations; + } + /** * Sets the player's appearance. * diff --git a/game/src/main/java/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java index b628ded53..26ac6fc11 100644 --- a/game/src/main/java/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java +++ b/game/src/main/java/org/apollo/game/release/r317/PlayerSynchronizationMessageEncoder.java @@ -9,6 +9,7 @@ import org.apollo.game.model.Item; import org.apollo.game.model.Position; import org.apollo.game.model.entity.EquipmentConstants; +import org.apollo.game.model.entity.AnimationMap; import org.apollo.game.model.entity.setting.Gender; import org.apollo.game.model.inv.Inventory; import org.apollo.game.sync.block.AnimationBlock; @@ -207,13 +208,14 @@ private static void putAppearanceBlock(AppearanceBlock block, GamePacketBuilder playerProperties.put(DataType.BYTE, color); } - playerProperties.put(DataType.SHORT, 0x328); // stand - playerProperties.put(DataType.SHORT, 0x337); // stand turn - playerProperties.put(DataType.SHORT, 0x333); // walk - playerProperties.put(DataType.SHORT, 0x334); // turn 180 - playerProperties.put(DataType.SHORT, 0x335); // turn 90 cw - playerProperties.put(DataType.SHORT, 0x336); // turn 90 ccw - playerProperties.put(DataType.SHORT, 0x338); // run + AnimationMap animations = block.getAnimations(); + playerProperties.put(DataType.SHORT, animations.getStand()); // stand + playerProperties.put(DataType.SHORT, animations.getIdleTurn()); // stand turn + playerProperties.put(DataType.SHORT, animations.getWalking()); // walk + playerProperties.put(DataType.SHORT, animations.getTurnAround()); // turn 180 + playerProperties.put(DataType.SHORT, animations.getTurnRight()); // turn 90 cw + playerProperties.put(DataType.SHORT, animations.getTurnLeft()); // turn 90 ccw + playerProperties.put(DataType.SHORT, animations.getRunning()); // run playerProperties.put(DataType.LONG, block.getName()); playerProperties.put(DataType.BYTE, block.getCombatLevel()); diff --git a/game/src/main/java/org/apollo/game/release/r377/PlayerSynchronizationMessageEncoder.java b/game/src/main/java/org/apollo/game/release/r377/PlayerSynchronizationMessageEncoder.java index 5ef61f80c..77d1f4017 100644 --- a/game/src/main/java/org/apollo/game/release/r377/PlayerSynchronizationMessageEncoder.java +++ b/game/src/main/java/org/apollo/game/release/r377/PlayerSynchronizationMessageEncoder.java @@ -2,36 +2,14 @@ import org.apollo.cache.def.EquipmentDefinition; import org.apollo.game.message.impl.PlayerSynchronizationMessage; -import org.apollo.game.model.Animation; -import org.apollo.game.model.Appearance; -import org.apollo.game.model.Direction; -import org.apollo.game.model.Graphic; -import org.apollo.game.model.Item; -import org.apollo.game.model.Position; +import org.apollo.game.model.*; import org.apollo.game.model.entity.EquipmentConstants; +import org.apollo.game.model.entity.AnimationMap; import org.apollo.game.model.entity.setting.Gender; import org.apollo.game.model.inv.Inventory; -import org.apollo.game.sync.block.AnimationBlock; -import org.apollo.game.sync.block.AppearanceBlock; -import org.apollo.game.sync.block.ChatBlock; -import org.apollo.game.sync.block.ForceChatBlock; -import org.apollo.game.sync.block.ForceMovementBlock; -import org.apollo.game.sync.block.GraphicBlock; -import org.apollo.game.sync.block.HitUpdateBlock; -import org.apollo.game.sync.block.InteractingMobBlock; -import org.apollo.game.sync.block.SecondaryHitUpdateBlock; -import org.apollo.game.sync.block.SynchronizationBlockSet; -import org.apollo.game.sync.block.TurnToPositionBlock; -import org.apollo.game.sync.seg.AddPlayerSegment; -import org.apollo.game.sync.seg.MovementSegment; -import org.apollo.game.sync.seg.SegmentType; -import org.apollo.game.sync.seg.SynchronizationSegment; -import org.apollo.game.sync.seg.TeleportSegment; -import org.apollo.net.codec.game.DataOrder; -import org.apollo.net.codec.game.DataTransformation; -import org.apollo.net.codec.game.DataType; -import org.apollo.net.codec.game.GamePacket; -import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.game.sync.block.*; +import org.apollo.game.sync.seg.*; +import org.apollo.net.codec.game.*; import org.apollo.net.meta.PacketType; import org.apollo.net.release.MessageEncoder; @@ -208,13 +186,14 @@ private static void putAppearanceBlock(AppearanceBlock block, GamePacketBuilder playerProperties.put(DataType.BYTE, color); } - playerProperties.put(DataType.SHORT, 0x328); // stand - playerProperties.put(DataType.SHORT, 0x337); // stand turn - playerProperties.put(DataType.SHORT, 0x333); // walk - playerProperties.put(DataType.SHORT, 0x334); // turn 180 - playerProperties.put(DataType.SHORT, 0x335); // turn 90 cw - playerProperties.put(DataType.SHORT, 0x336); // turn 90 ccw - playerProperties.put(DataType.SHORT, 0x338); // run + AnimationMap animations = block.getAnimations(); + playerProperties.put(DataType.SHORT, animations.getStand()); // stand + playerProperties.put(DataType.SHORT, animations.getIdleTurn()); // stand turn + playerProperties.put(DataType.SHORT, animations.getWalking()); // walk + playerProperties.put(DataType.SHORT, animations.getTurnAround()); // turn 180 + playerProperties.put(DataType.SHORT, animations.getTurnRight()); // turn 90 cw + playerProperties.put(DataType.SHORT, animations.getTurnLeft()); // turn 90 ccw + playerProperties.put(DataType.SHORT, animations.getRunning()); // run playerProperties.put(DataType.LONG, block.getName()); playerProperties.put(DataType.BYTE, block.getCombatLevel()); diff --git a/game/src/main/java/org/apollo/game/sync/block/AppearanceBlock.java b/game/src/main/java/org/apollo/game/sync/block/AppearanceBlock.java index a2a54356c..9a23d00b8 100644 --- a/game/src/main/java/org/apollo/game/sync/block/AppearanceBlock.java +++ b/game/src/main/java/org/apollo/game/sync/block/AppearanceBlock.java @@ -1,6 +1,7 @@ package org.apollo.game.sync.block; import org.apollo.game.model.Appearance; +import org.apollo.game.model.entity.AnimationMap; import org.apollo.game.model.inv.Inventory; /** @@ -10,6 +11,11 @@ */ public final class AppearanceBlock extends SynchronizationBlock { + /** + * The player's movement animations. + */ + private final AnimationMap animations; + /** * The player's appearance. */ @@ -53,32 +59,35 @@ public final class AppearanceBlock extends SynchronizationBlock { /** * Creates the appearance block. Assumes that the player is not appearing as an npc. * - * @param name The player's username, encoded to base 37. + * @param name The player's username, encoded to base 37. * @param appearance The {@link Appearance}. - * @param combat The player's combat. - * @param skill The player's skill, or 0 if showing the combat level. - * @param equipment The player's equipment. - * @param headIcon The head icon id of the player. - * @param isSkulled Whether or not the player is skulled. - */ - AppearanceBlock(long name, Appearance appearance, int combat, int skill, Inventory equipment, int headIcon, boolean isSkulled) { - this(name, appearance, combat, skill, equipment, headIcon, isSkulled, -1); + * @param combat The player's combat. + * @param skill The player's skill, or 0 if showing the combat level. + * @param equipment The player's equipment. + * @param headIcon The head icon id of the player. + * @param isSkulled Whether or not the player is skulled. + * @param animations + */ + AppearanceBlock(long name, Appearance appearance, int combat, int skill, Inventory equipment, int headIcon, boolean isSkulled, AnimationMap animations) { + this(name, animations, appearance, combat, skill, equipment, headIcon, isSkulled, -1); } /** * Creates the appearance block. * - * @param name The player's username, encoded to base 37. + * @param name The player's username, encoded to base 37. + * @param animations * @param appearance The {@link Appearance}. - * @param combat The player's combat. - * @param skill The player's skill, or 0 if showing the combat level. - * @param equipment The player's equipment. - * @param headIcon The prayer icon id of this player. - * @param isSkulled Whether or not the player is skulled. - * @param npcId The npc id of the player, if they are appearing as an npc, (otherwise {@code -1}). - */ - AppearanceBlock(long name, Appearance appearance, int combat, int skill, Inventory equipment, int headIcon, boolean isSkulled, int npcId) { + * @param combat The player's combat. + * @param skill The player's skill, or 0 if showing the combat level. + * @param equipment The player's equipment. + * @param headIcon The prayer icon id of this player. + * @param isSkulled Whether or not the player is skulled. + * @param npcId The npc id of the player, if they are appearing as an npc, (otherwise {@code -1}). + */ + AppearanceBlock(long name, AnimationMap animations, Appearance appearance, int combat, int skill, Inventory equipment, int headIcon, boolean isSkulled, int npcId) { this.name = name; + this.animations = animations; this.appearance = appearance; this.combat = combat; this.skill = skill; @@ -97,6 +106,15 @@ public boolean appearingAsNpc() { return npcId != -1; } + /** + * Gets the player's {@link AnimationMap}. + * + * @return The player's animations. + */ + public AnimationMap getAnimations() { + return animations; + } + /** * Gets the player's {@link Appearance}. * diff --git a/game/src/main/java/org/apollo/game/sync/block/SynchronizationBlock.java b/game/src/main/java/org/apollo/game/sync/block/SynchronizationBlock.java index 238ed7bfd..e664e3f1d 100644 --- a/game/src/main/java/org/apollo/game/sync/block/SynchronizationBlock.java +++ b/game/src/main/java/org/apollo/game/sync/block/SynchronizationBlock.java @@ -37,7 +37,7 @@ public static SynchronizationBlock createAppearanceBlock(Player player) { int combat = player.getSkillSet().getCombatLevel(); int id = player.hasNpcDefinition() ? player.getDefinition().getId() : -1; - return new AppearanceBlock(player.getEncodedName(), player.getAppearance(), combat, 0, player.getEquipment(), player.getPrayerIcon(), player.isSkulled(), id); + return new AppearanceBlock(player.getEncodedName(), player.getAnimations(), player.getAppearance(), combat, 0, player.getEquipment(), player.getPrayerIcon(), player.isSkulled(), id); } /** diff --git a/game/src/main/kotlin/stub.kt b/game/src/main/kotlin/stub.kt deleted file mode 100644 index 0f49676b1..000000000 --- a/game/src/main/kotlin/stub.kt +++ /dev/null @@ -1,28 +0,0 @@ -/** - * NOTE: This file is a stub, intended only for use within an IDE. It should be updated - * each time [org.apollo.game.plugin.kotlin.KotlinPluginScript] has a new method added to it. - * - * Until IntelliJ IDEA starts to support ScriptTemplateDefinitions this is - * required to resolve references within plugin code. - */ - -import org.apollo.game.command.Command -import org.apollo.game.message.impl.ButtonMessage -import org.apollo.game.model.World -import org.apollo.game.model.entity.setting.PrivilegeLevel -import org.apollo.game.model.event.Event -import org.apollo.game.model.event.PlayerEvent -import org.apollo.game.plugin.kotlin.KotlinEventHandler -import org.apollo.game.plugin.kotlin.KotlinPlayerHandlerProxyTrait -import org.apollo.net.message.Message -import kotlin.reflect.KClass - -fun on(type: () -> KClass): KotlinPlayerHandlerProxyTrait = null!! -fun on_player_event(type: () -> KClass): KotlinPlayerHandlerProxyTrait = null!! -fun on_event(type: () -> KClass): KotlinEventHandler = null!! -fun on_command(command: String, privileges: PrivilegeLevel): KotlinPlayerHandlerProxyTrait = null!! -fun on_button(button: Int): KotlinPlayerHandlerProxyTrait = null!! - -fun start(callback: (World) -> Unit) {} -fun stop(callback: (World) -> Unit) = {} -