Skip to content

Commit 56645c7

Browse files
committed
added new enemy
1 parent 72fa3e6 commit 56645c7

File tree

7 files changed

+126
-15
lines changed

7 files changed

+126
-15
lines changed

actions.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,64 @@ def perform(self) -> None:
244244
if self.entity.equipment.item_is_equipped(self.item):
245245
self.entity.equipment.toggle_equip(self.item)
246246

247-
self.entity.inventory.drop(self.item)
247+
self.entity.inventory.drop(self.item)
248+
249+
class PounceAction(Action):
250+
def __init__(self, entity: Actor, x: int, y: int, effect:StatusEffect):
251+
super().__init__(entity)
252+
253+
self.x = x
254+
self.y = y
255+
self.effect = effect
256+
257+
def perform(self) -> None:
258+
self.entity.move(self.x - self.entity.x, self.y - self.entity.y)
259+
260+
target = None
261+
for entity in self.engine.game_map.actors:
262+
if entity.x == self.x and entity.y == self.y:
263+
target = entity
264+
break
265+
if not target:
266+
self.engine.message_log.add_message(f"{self.entity.name} pounces, but misses the target.")
267+
else:
268+
damage = (self.entity.fighter.power ** 2) - target.fighter.defense # Deal 2x the power of the attacker's power
269+
270+
# If the attacker is the player and godmode is on, then the attacker will do max damage
271+
if self.entity is self.engine.player and self.engine.game_world.godmode:
272+
damage = 99999999
273+
274+
attack_desc = f"{self.entity.name.capitalize()} pounces and attacks {target.name.capitalize()}"
275+
if self.entity is self.engine.player:
276+
attack_color = color.player_atk
277+
else:
278+
attack_color = color.enemy_atk
279+
if damage > 0:
280+
281+
# if the target is the player and godmode is on, then the target will take no damage
282+
if target is self.engine.player and self.engine.game_world.godmode:
283+
return
284+
285+
self.engine.message_log.add_message(
286+
f"{attack_desc} for {damage} hit points!",
287+
attack_color
288+
)
289+
target.fighter.hp -= damage
290+
target.fighter.last_actor_hurt_by = self.entity.internal_name
291+
292+
# trigger any on_attack of the equipment of the attacker, if any.
293+
if self.entity.equipment and self.entity.equipment.weapon:
294+
self.entity.equipment.weapon.equippable.on_attack(target)
295+
296+
# trigger any on_hit of the equipment of the target, if any.
297+
if target.equipment and target.equipment.armor:
298+
target.equipment.armor.equippable.on_hit(self.entity, damage)
299+
300+
# Apply the status effect to the target, if any.
301+
if self.effect is not None:
302+
target.fighter.apply_status_effect(self.effect)
303+
else:
304+
self.engine.message_log.add_message(
305+
f"{attack_desc}, but does no damage.",
306+
attack_color
307+
)

components/ai.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import numpy as np # type: ignore
77
import tcod
88

9-
from actions import Action, MeleeAction, MovementAction, WaitAction
9+
from actions import Action, MeleeAction, MovementAction, PounceAction, WaitAction
1010
from entity import Actor
1111

1212

@@ -19,6 +19,7 @@ class BaseAI(Action):
1919

2020
def __init__(self, entity: Actor) -> None:
2121
super().__init__(entity)
22+
self.effect = None
2223

2324
def set_effect(self, effect: Optional[StatusEffect] = None) -> None:
2425
self.effect = effect
@@ -254,5 +255,36 @@ def perform(self) -> None:
254255
# Add 1 to the spawn timer.
255256
self.spawn_timer += 1
256257

258+
# Return a wait action.
259+
return WaitAction(self.entity).perform()
260+
261+
class InvisblePouncerEnemy(BaseAI):
262+
"""
263+
A invisible pouncer enemy will pounce on the player if they are in some amount tiles around it. Until then, it won't move and will be invisible until the player is in range. After that it will switch to the selected AI class.
264+
"""
265+
266+
def __init__(
267+
self, entity: Actor
268+
):
269+
super().__init__(entity)
270+
entity.visible = False
271+
self.is_setup = False
272+
273+
def setup(self, prev_ai: type[BaseAI], pounce_distance: int):
274+
self.pounce_distance = pounce_distance
275+
self.prev_ai = prev_ai(self.entity)
276+
self.is_setup = True
277+
278+
def perform(self) -> None:
279+
# Do not perform if this AI is not setup or the enemy is not visible in FOV.
280+
if not self.is_setup or not self.engine.game_map.visible[self.entity.x, self.entity.y]:
281+
return WaitAction(self.entity).perform()
282+
283+
# If the player is in range, become visble and try to pounce on them.
284+
if self.entity.distance(self.engine.player.x, self.engine.player.y) <= self.pounce_distance:
285+
self.entity.visible = True
286+
self.entity.ai = self.prev_ai
287+
return PounceAction(self.entity, self.engine.player.x, self.engine.player.y, self.effect).perform()
288+
257289
# Return a wait action.
258290
return WaitAction(self.entity).perform()

entity.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def __init__(
5757
self.inspect_message = inspect_message
5858
self.is_swarm = False
5959
self.last_position = (x, y)
60+
self.visible = True
6061

6162
# Create a internal_name for the entity. Internal names are allways lowercase and have spaces replaced with underscores.
6263
self.internal_name = name.lower().replace(" ", "_")

entity_factories.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from components.ai import HostileEnemy, SpawnerEnemy, ZoneSpawnerEnemy
1+
from components.ai import HostileEnemy, SpawnerEnemy, ZoneSpawnerEnemy, InvisblePouncerEnemy
22
from components import consumable, equippable
33
from components.fighter import Fighter
44
from components.inventory import Inventory
@@ -290,4 +290,17 @@
290290
inspect_message="It's a large mushroom, like the little baby ones. It's releasing spores from it's cap. Is that a bad thing?",
291291
)
292292
bloom_shroom.ai.setup(spore_filled_air, 3)
293-
ALL_ENTITIES.append(bloom_shroom)
293+
ALL_ENTITIES.append(bloom_shroom)
294+
wild_hunter_humanoid = Actor(
295+
char="h",
296+
color=(51, 0, 51),
297+
name="Wild Hunter Humanoid",
298+
ai_cls=InvisblePouncerEnemy,
299+
fighter=Fighter(hp=30, base_power=4, base_dodge=15, base_defence=2),
300+
inventory=Inventory(capacity=0),
301+
level=Level(xp_given=250),
302+
equipment=Equipment(),
303+
inspect_message="It's a hunter humanoid that looks a lot more scarier! Dark fur, red blood eyes and long fangs make it look like a monster.",
304+
)
305+
wild_hunter_humanoid.ai.setup(HostileEnemy, 5)
306+
ALL_ENTITIES.append(wild_hunter_humanoid)

game_map.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,8 @@ def render(self, console: Console) -> None:
243243
)
244244

245245
for entity in entities_sorted_for_rendering:
246-
# Only print entities that are in the FOV
247-
if self.visible[entity.x, entity.y]:
246+
# Only print entities that are in the FOV or are visible.
247+
if self.visible[entity.x, entity.y] and entity.visible == True:
248248
console.print(
249249
x=entity.x, y=entity.y, string=entity.char, fg=entity.color
250250
)
@@ -287,13 +287,7 @@ def __init__(
287287

288288
self.player_confused_turns = 0
289289

290-
def generate_floor(self) -> None:
291-
from procgen import generate_dungeon, generate_shopkeep_floor
292-
293-
floor_type = "normal"
294-
295-
self.current_floor += 1
296-
290+
def update_floor_colors(self) -> None:
297291
if self.current_floor < 10:
298292
# Use "The lab" colors for the tiles
299293
color.current_bg = color.bg_lab
@@ -309,6 +303,14 @@ def generate_floor(self) -> None:
309303
tile_types.SHROUD["fg"] = color.gray_scale_color(color.bg_grotto)
310304
tile_types.SHROUD["bg"] = color.bg_grotto
311305

306+
def generate_floor(self) -> None:
307+
from procgen import generate_dungeon, generate_shopkeep_floor
308+
309+
floor_type = "normal"
310+
311+
self.current_floor += 1
312+
313+
self.update_floor_colors()
312314

313315
if self.current_floor == 1:
314316
pass # Guarantee the first floor is normal

procgen.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
8: [(entity_factories.rusty_automaton, 0), (entity_factories.hunter_humanoid, 65)],
4545
9: [(entity_factories.slime_mold, 0)],
4646
10: [(entity_factories.baby_shroom, 25)],
47-
12: [(entity_factories.bloom_shroom, 15)]
47+
12: [(entity_factories.bloom_shroom, 15)],
48+
13: [(entity_factories.wild_hunter_humanoid, 25), (entity_factories.hunter_humanoid, 35)]
4849
}
4950

5051
def get_max_value_for_floor(

setup_game.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ def ev_keydown(
116116
raise SystemExit()
117117
elif event.sym == tcod.event.KeySym.c:
118118
try:
119-
return input_handlers.MainGameEventHandler(load_game("savegame.sav"))
119+
engine = load_game("savegame.sav")
120+
engine.game_world.update_floor_colors()
121+
return input_handlers.MainGameEventHandler(engine)
120122
except FileNotFoundError:
121123
return input_handlers.PopupMessage(self, "No saved game to load.")
122124
except Exception as exc:

0 commit comments

Comments
 (0)