diff --git a/data/definitions/animations.yml b/data/definitions/animations.yml index 371dbd3f0f..d7cb670676 100644 --- a/data/definitions/animations.yml +++ b/data/definitions/animations.yml @@ -1668,3 +1668,10 @@ fletching_string_steel_crossbow: 6674 fletching_string_mithril_crossbow: 6675 fletching_string_adamant_crossbow: 6676 fletching_string_rune_crossbow: 6677 +dig_with_spade: 830 +giant_mole_burrow: 3314 +giant_mole_attack: 3312 +giant_mole_hit: 3311 +giant_mole_death: 3310 +dirt_projectile: 570 +giant_mole_burrow_up: 1664 \ No newline at end of file diff --git a/data/definitions/graphics.yml b/data/definitions/graphics.yml index 284cec2563..cdcd570a51 100644 --- a/data/definitions/graphics.yml +++ b/data/definitions/graphics.yml @@ -1480,4 +1480,5 @@ dragon_breath_shock: delay: 51 flight_time: [ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 ] teleport_jewellery: 1684 -teleport_pharaohs_sceptre: 715 \ No newline at end of file +teleport_pharaohs_sceptre: 715 +burrow_dust: 571 \ No newline at end of file diff --git a/data/definitions/interfaces.yml b/data/definitions/interfaces.yml index a4c729c711..4c75a5aed6 100644 --- a/data/definitions/interfaces.yml +++ b/data/definitions/interfaces.yml @@ -1756,7 +1756,12 @@ dialogue_multi2_mes: type: dialogue_box warning_spiders: 560 warning_kalphite_queen: 561 -warning_dark: 562 +warning_dark: + id: 562 + type: main_screen + components: + proceed: 17 + stayout: 18 warning_building_mode: 563 warning_archers: 564 warning_desert: 565 @@ -2517,3 +2522,15 @@ fade_in: fade_out_smoke: id: 162 type: overlay +level_one_darkness: + id: 97 + type: overlay +level_two_darkness: + id: 98 + type: overlay +level_three_darkness: + id: 96 + type: overlay +dirt_on_screen: + id: 226 + type: overlay diff --git a/data/definitions/npcs.yml b/data/definitions/npcs.yml index f5e75b5da2..ab890ae1b5 100644 --- a/data/definitions/npcs.yml +++ b/data/definitions/npcs.yml @@ -1829,4 +1829,20 @@ al_the_camel: race: camel large_head: true wander_radius: 4 - examine: "A camel who has the soul of a poet." \ No newline at end of file + examine: "A camel who has the soul of a poet." +giant_mole: + id: 3340 + hitpoints: 2000 + att: 200 + str: 200 + def: 200 + max_hit_melee: 210 + style: crush + race: giant_mole + respawn_delay: 16 + wander_radius: 8 + examine: "Holy Mole-y!" +baby_mole: + id: 3341 + wander_radius: 6 + examine: "I will call him, Mini Mole." diff --git a/data/definitions/objects.yml b/data/definitions/objects.yml index b9c01941dc..ca79ed8d05 100644 --- a/data/definitions/objects.yml +++ b/data/definitions/objects.yml @@ -11703,4 +11703,6 @@ prayer_altar_zaros: examine: "Shrine to the glory of Zaros." prayer_altar_warped_construction: id: 55667 - examine: "A shrine for the faithful to worship at." \ No newline at end of file + examine: "A shrine for the faithful to worship at." +giant_mole_lair_escape_rope: + id: 12230 \ No newline at end of file diff --git a/data/definitions/sounds.yml b/data/definitions/sounds.yml index b1255d0cdb..26fb4738dd 100644 --- a/data/definitions/sounds.yml +++ b/data/definitions/sounds.yml @@ -194,4 +194,9 @@ knife_throw: 2707 thrown: 2708 godwars_saradomin_magic_impact: 3853 godwars_godsword_special_attack: 3865 -pottery: 2588 \ No newline at end of file +pottery: 2588 +giant_mole_burrow_down: 1641 +giant_mole_attack: 1642 +giant_mole_hit: 1646 +giant_mole_burrow: 1643 +giant_mole_death: 1645 \ No newline at end of file diff --git a/data/map/areas.yml b/data/map/areas.yml index 37fede159a..8ae3c3fc0d 100644 --- a/data/map/areas.yml +++ b/data/map/areas.yml @@ -977,4 +977,14 @@ camulet_teleport: area: x: [ 3192, 3194 ] y: [ 2924, 2926 ] - tags: [ teleport ] \ No newline at end of file + tags: [ teleport ] +giant_mole_lair: + area: + x: [ 1717, 1801 ] + y: [ 5123, 5252 ] + tags: [ boss ] +giant_mole_spawn_area: + area: + x: [ 1733, 1788 ] + y: [ 5150, 5223 ] + tags: [ boss ] \ No newline at end of file diff --git a/data/spawns/drops.yml b/data/spawns/drops.yml index 896d07d266..f5f243c114 100644 --- a/data/spawns/drops.yml +++ b/data/spawns/drops.yml @@ -889,4 +889,171 @@ human_drop_table: drops: - type: all drops: - - id: bones \ No newline at end of file + - id: bones +giant_mole_drop_table: + type: all + drops: + - type: all + drops: + - id: big_bones + - id: mole_claw + - id: mole_skin + amount: 1-3 + - roll: 6 + drops: + - id: air_rune + amount: 105 + chance: 1 + - id: blood_rune + amount: 15 + chance: 1 + - roll: 11 + drops: + - id: fire_rune + amount: 105 + chance: 1 + - roll: 12 + drops: + - id: adamant_longsword + chance: 1 + - id: iron_arrow + amount: 690 + chance: 1 + - id: yew_logs_noted + amount: 100 + chance: 1 + - roll: 14 + drops: + - id: mithril_platebody + chance: 1 + - roll: 18 + drops: + - id: amulet_of_strength + chance: 1 + - roll: 25 + drops: + - id: law_rune + amount: 15 + chance: 1 + - roll: 32 + drops: + - id: shark + amount: 4 + chance: 1 + - roll: 34 + drops: + - id: nothing + chance: 1 + - roll: 42 + drops: + - id: mithril_bar + chance: 1 + - id: death_rune + amount: 7 + chance: 1 + - roll: 64 + drops: + - id: mithril_hatchet + chance: 1 + - id: iron_ore_noted + amount: 100 + chance: 1 + - roll: 77 + drops: + - id: uncut_sapphire + chance: 1 + - roll: 128 + drops: + - id: mithril_battleaxe + chance: 1 + - id: rune_med_helm + chance: 1 + - id: oyster_pearls + chance: 1 + - roll: 154 + drops: + - id: uncut_emerald + chance: 1 + - roll: 189 + drops: + - id: loop_half_of_a_key + chance: 1 + - id: tooth_half_of_a_key + chance: 1 + - roll: 195 + drops: + - id: coins + amount: 3000 + chance: 1 + - roll: 309 + drops: + - id: uncut_ruby + chance: 1 + - roll: 819 + drops: + - id: rune_bar + chance: 1 + - roll: 824 + drops: + - id: nature_talisman + chance: 1 + - roll: 1237 + drops: + - id: uncut_diamond + chance: 1 + - roll: 1365 + drops: + - id: nature_rune + amount: 67 + chance: 1 + - id: rune_2h_sword + chance: 1 + - id: rune_battle_axe + chance: 1 + - roll: 2048 + drops: + - id: law_rune + amount: 45 + chance: 1 + - id: death_rune + amount: 45 + chance: 1 + - id: steel_arrow + amount: 150 + chance: 1 + - id: rune_arrow + amount: 42 + chance: 1 + - id: adamant_javelin + amount: 20 + chance: 1 + - id: rune_sq_shield + chance: 1 + - id: dragonstone + chance: 1 + - id: silver_ore_noted + amount: 100 + chance: 1 + - roll: 2473 + drops: + - id: rune_javelin + amount: 5 + chance: 1 + - roll: 3935 + drops: + - id: rune_spear + chance: 1 + - roll: 4096 + drops: + - id: rune_kiteshield + chance: 1 + - id: dragon_med_helm + chance: 1 + - roll: 7869 + drops: + - id: shield_left_half + chance: 1 + - roll: 10492 + drops: + - id: dragon_spear + chance: 1 \ No newline at end of file diff --git a/data/spawns/npc-spawns.yml b/data/spawns/npc-spawns.yml index 78ea382e06..2dc6d7ddb1 100644 --- a/data/spawns/npc-spawns.yml +++ b/data/spawns/npc-spawns.yml @@ -1,5 +1,37 @@ # 6743 - { id: banker_fist_of_guthix, x: 1705, y: 5599 } +# 6993 +- { id: giant_mole, x: 1762, y: 5185 } +- { id: baby_mole, x: 1763, y: 5163 } +- { id: baby_mole, x: 1760, y: 5167 } +- { id: baby_mole, x: 1759, y: 5161 } +- { id: baby_mole, x: 1764, y: 5184 } +- { id: baby_mole, x: 1759, y: 5193 } +- { id: baby_mole, x: 1758, y: 5184 } +- { id: baby_mole, x: 1756, y: 5179 } +- { id: baby_mole, x: 1741, y: 5186 } +- { id: baby_mole, x: 1754, y: 5197 } +- { id: baby_mole, x: 1761, y: 5197 } +- { id: baby_mole, x: 1774, y: 5204 } +- { id: baby_mole, x: 1782, y: 5217 } +- { id: baby_mole, x: 1781, y: 5232 } +- { id: baby_mole, x: 1771, y: 5220 } +- { id: baby_mole, x: 1763, y: 5215 } +- { id: baby_mole, x: 1756, y: 5221 } +- { id: baby_mole, x: 1741, y: 5220 } +- { id: baby_mole, x: 1738, y: 5209 } +- { id: baby_mole, x: 1744, y: 5205 } +- { id: baby_mole, x: 1751, y: 5207 } +- { id: baby_mole, x: 1755, y: 5199 } +- { id: baby_mole, x: 1774, y: 5174 } +- { id: baby_mole, x: 1777, y: 5188 } +- { id: baby_mole, x: 1775, y: 5194 } +- { id: baby_mole, x: 1775, y: 5166 } +- { id: baby_mole, x: 1762, y: 5151 } +- { id: baby_mole, x: 1778, y: 5149 } +- { id: baby_mole, x: 1744, y: 5151 } +- { id: baby_mole, x: 1737, y: 5157 } +- { id: baby_mole, x: 1748, y: 5168 } # 7243 - { id: rat, x: 1816, y: 4836, level: 1, members: true } - { id: rat, x: 1815, y: 4832, members: true } diff --git a/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/npc/combat/type/GiantMole.kts b/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/npc/combat/type/GiantMole.kts new file mode 100644 index 0000000000..fcf1c701df --- /dev/null +++ b/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/npc/combat/type/GiantMole.kts @@ -0,0 +1,211 @@ +package world.gregs.voidps.world.interact.entity.npc.combat.type + +import com.github.michaelbull.logging.InlineLogger +import world.gregs.voidps.engine.client.ui.close +import world.gregs.voidps.engine.client.ui.closeInterfaces +import world.gregs.voidps.engine.client.ui.interfaceOption +import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.client.variable.hasClock +import world.gregs.voidps.engine.client.variable.start +import world.gregs.voidps.engine.data.definition.AreaDefinitions +import world.gregs.voidps.engine.entity.World +import world.gregs.voidps.engine.entity.character.face +import world.gregs.voidps.engine.entity.character.facing +import world.gregs.voidps.engine.entity.character.mode.EmptyMode +import world.gregs.voidps.engine.entity.character.mode.move.enterArea +import world.gregs.voidps.engine.entity.character.mode.move.exitArea +import world.gregs.voidps.engine.entity.character.move.tele +import world.gregs.voidps.engine.entity.character.npc.NPC +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.Players +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.setAnimation +import world.gregs.voidps.engine.entity.obj.objectOperate +import world.gregs.voidps.engine.entity.playerSpawn +import world.gregs.voidps.engine.inject +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.itemChange +import world.gregs.voidps.engine.inv.transact.operation.ReplaceItem.replace +import world.gregs.voidps.engine.map.collision.random +import world.gregs.voidps.engine.queue.queue +import world.gregs.voidps.type.Direction +import world.gregs.voidps.type.Tile +import world.gregs.voidps.world.interact.entity.combat.attackers +import world.gregs.voidps.world.interact.entity.combat.fightStyle +import world.gregs.voidps.world.interact.entity.combat.hit.npcCombatHit +import world.gregs.voidps.world.interact.entity.gfx.areaGraphic +import world.gregs.voidps.world.interact.entity.player.equip.inventoryItem +import world.gregs.voidps.world.interact.entity.sound.areaSound +import kotlin.random.Random + +val logger = InlineLogger() +val areas: AreaDefinitions by inject() +val players: Players by inject() + +val acceptedTiles = listOf( + Tile(3005, 3376, 0), + Tile(2999, 3375, 0), + Tile(2996, 3377, 0), + Tile(2989, 3378, 0) +) + +val giantMoleLair = areas["giant_mole_lair"] +val gianMoleSpawns = areas["giant_mole_spawn_area"] +val initialCaveTile: Tile = Tile(1752, 5237, 0) + +inventoryItem("Dig", "spade") { + val playerTile: Tile = player.tile + player.setAnimation("dig_with_spade") + if (!acceptedTiles.contains(playerTile)) { + return@inventoryItem + } + player.open("warning_dark") +} + +objectOperate("Climb", "giant_mole_lair_escape_rope") { + player.setAnimation("climb_up") + player.tele(acceptedTiles.random()) +} + +interfaceOption(component = "proceed", id = "warning_dark") { + player.tele(initialCaveTile, clearInterfaces = true) +} + +interfaceOption(component = "stayout", id = "warning_dark") { + player.closeInterfaces() +} + +npcCombatHit("giant_mole") { + val currentHealth = it.levels.get(Skill.Constitution) + var shouldBurrow = false + if (it.fightStyle == "magic" && damage != 0) { + shouldBurrow = shouldBurrowAway(currentHealth) + } else if (it.fightStyle != "magic") { + shouldBurrow = shouldBurrowAway(currentHealth) + } + if (shouldBurrow && !it.hasClock("awaiting_mole_burrow_complete")) { + it.start("awaiting_mole_burrow_complete", 4) + giantMoleBurrow(it) + } +} + +fun giantMoleBurrow(mole: NPC) { + for (attacker in mole.attackers) { + attacker.mode = EmptyMode + } + mole.attackers.clear() + var tileToDust = mole.tile.add(getRandomFacing(mole.facing).delta) + mole.queue("await_mole_to_face", 1) { + if (tileToDust == Tile.EMPTY) { + logger.info { "failed to get facing tile for Giant Mole, using default tile." } + tileToDust = initialCaveTile + } + mole.face(tileToDust) + mole.queue("display_burrow_dust", 1) { + if (shouldThrowDirt()) { + handleDirtOnScreen(mole.tile) + } + mole.setAnimation("giant_mole_burrow") + areaSound("giant_mole_burrow_down", mole.tile) + areaGraphic("burrow_dust", tileToDust) + mole.queue("await_mole_burrowing", 1) { + val newLocation = gianMoleSpawns.random(mole) + mole.tele(newLocation!!) + mole.setAnimation("mole_burrow_up") + } + } + } +} + +fun getRandomFacing(currentlyFacing: Direction): Direction { + var randomDirection: Direction + do { + randomDirection = Direction.entries.filter { it != Direction.NONE }.random() + } while (randomDirection == currentlyFacing) + return randomDirection +} + + +// 13% chance to throw dirt on players screen +fun shouldThrowDirt(): Boolean { + val dirtChance = Random.nextInt(0, 100) + return dirtChance <= 13 +} + +fun handleDirtOnScreen(moleTile: Tile) { + val nearMole = mutableListOf() + for (tile in moleTile.toCuboid(5)) { + for (player in players[tile]) { + nearMole.add(player) + } + } + for (player in nearMole) { + player.open("dirt_on_screen") + player.inventory.transaction { + for (index in inventory.indices) { + val item = inventory[index] + if (item.id.endsWith("candle_lit")) { + replace(item.id, item.id.removeSuffix("_lit")) + } + } + } + } + World.queue("dirt_on_screen_timer_player", 3) { + for (player in nearMole) { + player.close("dirt_on_screen") + } + } +} + +fun shouldBurrowAway(health: Int): Boolean { + val maxHealth = 2000 + val minThreshold = maxHealth * 0.05 + val maxThreshold = maxHealth * 0.50 + if (health in minThreshold.toInt()..maxThreshold.toInt()) { + val shouldBurrow = Random.nextInt(0, 100) + return shouldBurrow <= 25 + } + return false +} + +enterArea("giant_mole_lair") { + if (!hasLightSource(player)) { + player.open("level_three_darkness") + } +} + +exitArea("giant_mole_lair") { + if (player.interfaces.contains("level_three_darkness")) { + player.close("level_three_darkness") + } +} + +playerSpawn { player -> + if (giantMoleLair.contains(player.tile)) { + if (!hasLightSource(player)) { + player.open("level_three_darkness") + } + } +} + +itemChange("inventory") { player: Player -> + if (giantMoleLair.contains(player.tile)) { + val hasLightSource = hasLightSource(player) + if (!hasLightSource && !player.interfaces.contains("level_three_darkness")) { + player.open("level_three_darkness") + } else if (hasLightSource && player.interfaces.contains("level_three_darkness")) { + player.close("level_three_darkness") + } + } +} + +fun hasLightSource(player: Player): Boolean { + val playerItems = player.inventory.items + + for (item in playerItems) { + if (item.id.endsWith("lantern_lit") || item.id.endsWith("candle_lit")) { + return true + } + } + return false +} \ No newline at end of file