Skip to content

Commit 200a936

Browse files
committed
separate combat and rest sites
1 parent 5fab8e7 commit 200a936

File tree

5 files changed

+336
-310
lines changed

5 files changed

+336
-310
lines changed

combat.py

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import random
2+
from time import sleep
3+
4+
import card_catalog
5+
import displayer as view
6+
import effect_interface as ei
7+
import game_map
8+
import generators as gen
9+
import potion_catalog
10+
from ansi_tags import ansiprint
11+
from definitions import CombatTier, State, TargetType
12+
from enemy import Enemy
13+
from enemy_catalog import (
14+
create_act1_boss,
15+
create_act1_elites,
16+
create_act1_normal_encounters,
17+
)
18+
from message_bus_tools import Message, bus
19+
from player import Player
20+
21+
22+
class Combat:
23+
def __init__(self, tier: CombatTier, player: Player, game_map: game_map.GameMap, all_enemies: list[Enemy] | None = None):
24+
self.tier = tier
25+
self.player = player
26+
self.all_enemies = all_enemies if all_enemies else []
27+
self.previous_enemy_states = ()
28+
self.death_messages = []
29+
self.turn = 1
30+
self.game_map = game_map
31+
32+
@property
33+
def active_enemies(self):
34+
return [enemy for enemy in self.all_enemies if enemy.state == State.ALIVE]
35+
36+
def combat(self) -> None:
37+
"""There's too much to say here."""
38+
self.start_combat()
39+
# Combat automatically ends when all enemies are dead.
40+
while len(self.active_enemies) > 0:
41+
bus.publish(Message.START_OF_TURN, (self.turn, self.player))
42+
while True:
43+
self.on_player_move()
44+
if all((enemy.state == State.DEAD for enemy in self.all_enemies)):
45+
self.end_combat(killed_enemies=True)
46+
break
47+
48+
print(f"Turn {self.turn}: ")
49+
# Shows the player's potions, cards(in hand), amount of cards in discard and draw pile, and shows the status for you and the enemies.
50+
view.display_ui(self.player, self.active_enemies)
51+
print("1-0: Play card, P: Play Potion, M: View Map, D: View Deck, A: View Draw Pile, S: View Discard Pile, X: View Exhaust Pile, E: End Turn, F: View Debuffs and Buffs")
52+
action = input("> ").lower()
53+
other_options = {
54+
"d": lambda: view.view_piles(self.player.deck, end=True),
55+
"a": lambda: view.view_piles(
56+
self.player.draw_pile, shuffle=True, end=True
57+
),
58+
"s": lambda: view.view_piles(self.player.discard_pile, end=True),
59+
"x": lambda: view.view_piles(self.player.exhaust_pile, end=True),
60+
"p": self.play_potion,
61+
"f": lambda: ei.full_view(self.player, self.active_enemies),
62+
"m": lambda: view.view_map(self.game_map),
63+
}
64+
if action.isdigit():
65+
option = int(action) - 1
66+
if option + 1 in range(len(self.player.hand) + 1):
67+
self.play_new_card(self.player.hand[option])
68+
else:
69+
view.clear()
70+
continue
71+
elif action in other_options:
72+
other_options[action]()
73+
elif action == "e":
74+
view.clear()
75+
break
76+
else:
77+
view.clear()
78+
continue
79+
sleep(1)
80+
view.clear()
81+
if self.player.state == State.ESCAPED:
82+
self.end_combat(self, escaped=True)
83+
bus.publish(Message.END_OF_TURN, data=(self.player, self.all_enemies))
84+
self.turn += 1
85+
86+
def end_combat(self, killed_enemies=False, escaped=False, robbed=False):
87+
if killed_enemies is True:
88+
potion_roll = random.random()
89+
ansiprint("<green>Combat finished!</green>")
90+
self.player.gain_gold(random.randint(10, 20))
91+
if (potion_roll < self.player.potion_dropchance):
92+
gen.claim_potions(True, 1, self.player, potion_catalog.create_all_potions())
93+
self.player.potion_dropchance -= 10
94+
else:
95+
self.player.potion_dropchance += 10
96+
gen.card_rewards(self.tier, True, self.player, card_catalog.create_all_cards())
97+
view.clear()
98+
elif escaped is True:
99+
print("Escaped...")
100+
sleep(0.8)
101+
print("You recieve nothing.")
102+
sleep(1.5)
103+
view.clear()
104+
elif robbed:
105+
print("Robbed...")
106+
sleep(0.8)
107+
print("You recieve nothing.")
108+
sleep(1.2)
109+
view.clear()
110+
bus.publish(Message.END_OF_COMBAT, (self.tier, self.player))
111+
self.player.unsubscribe()
112+
for enemy in self.all_enemies:
113+
enemy.unsubscribe()
114+
115+
def on_player_move(self):
116+
self.update_death_messages()
117+
self.previous_enemy_states = tuple(enemy.state for enemy in self.all_enemies)
118+
119+
def clean_effects(effects):
120+
for effect in effects:
121+
if effect.amount <= 0:
122+
effect.unsubscribe()
123+
ansiprint(f"{effect.get_name()} wears off.")
124+
return [effect for effect in effects if effect.amount >= 1]
125+
126+
for enemy in self.active_enemies:
127+
enemy.buffs = clean_effects(enemy.buffs)
128+
enemy.debuffs = clean_effects(enemy.debuffs)
129+
self.player.buffs, self.player.debuffs = [
130+
clean_effects(effects) for effects in (self.player.buffs, self.player.debuffs)
131+
]
132+
133+
def create_enemies_from_tier(self) -> list[Enemy]:
134+
act1_normal_encounters = create_act1_normal_encounters(self.all_enemies)
135+
act1_elites = create_act1_elites()
136+
act1_boss = create_act1_boss()
137+
encounter_types = {
138+
CombatTier.NORMAL: act1_normal_encounters,
139+
CombatTier.ELITE: act1_elites,
140+
CombatTier.BOSS: act1_boss,
141+
}
142+
return encounter_types[self.tier][0]
143+
144+
def start_combat(self) -> list[Enemy]:
145+
self.player.register(bus=bus)
146+
if not self.all_enemies:
147+
self.all_enemies = self.create_enemies_from_tier()
148+
149+
for enemy in self.all_enemies:
150+
enemy.register(bus=bus)
151+
152+
bus.publish(Message.START_OF_COMBAT, (self.tier, self.active_enemies, self.player))
153+
self.previous_enemy_states = tuple(enemy.state for enemy in self.all_enemies)
154+
155+
def select_target(self):
156+
if len(self.active_enemies) == 1:
157+
return 0
158+
else:
159+
while True:
160+
try:
161+
target = int(input("Choose an enemy to target > ")) - 1
162+
_ = self.active_enemies[target]
163+
except (IndexError, ValueError):
164+
ansiprint(f"\u001b[1A\u001b[100D<red>You have to enter a number between 1 and {len(self.active_enemies)}</red>", end="")
165+
sleep(1)
166+
print("\u001b[2K\u001b[100D", end="")
167+
continue
168+
return target
169+
170+
def play_new_card(self, card):
171+
# Prevents the player from using a card that they don't have enough energy for.
172+
if card.energy_cost > self.player.energy:
173+
ansiprint("<red>You don't have enough energy to use this card.</red>")
174+
sleep(1)
175+
view.clear()
176+
return
177+
# Todo: Move to Velvet Choker relic
178+
if self.player.choker_cards_played == 6:
179+
ansiprint("You have already played 6 cards this turn!")
180+
sleep(1)
181+
view.clear()
182+
return
183+
184+
if card.target == TargetType.SINGLE:
185+
target = self.select_target()
186+
self.player.use_card(card, target=self.active_enemies[target], exhaust=False, pile=self.player.hand, enemies=self.all_enemies)
187+
else:
188+
self.player.use_card(card, target=self.active_enemies, exhaust=False, pile=self.player.hand, enemies=self.all_enemies)
189+
190+
def update_death_messages(self):
191+
current_states = tuple(enemy.state for enemy in self.all_enemies)
192+
for i in range(0, max(1, len(self.all_enemies) - 1)):
193+
if self.previous_enemy_states[i] != current_states[i]:
194+
self.death_messages.append(current_states[i])
195+
196+
def play_potion(self):
197+
if len(self.player.potions) == 0:
198+
ansiprint("<red>You have no potions.</red>")
199+
return
200+
chosen_potion = view.list_input("Choose a potion to play", self.player.potions, view.view_potions, lambda potion: potion.playable, "That potion is not playable.")
201+
potion = self.player.potions.pop(chosen_potion)
202+
if potion.target == TargetType.YOURSELF:
203+
potion.apply(self.player)
204+
elif potion.target == TargetType.SINGLE:
205+
target = self.select_target()
206+
potion.apply(self.player, self.active_enemies[target])
207+
elif potion.target in (TargetType.AREA, TargetType.ANY):
208+
potion.apply(self.player, self.active_enemies)
209+

0 commit comments

Comments
 (0)