diff --git a/game/content/ghplots/mission_bigobs.py b/game/content/ghplots/mission_bigobs.py index 33b45773..2ca4cc37 100644 --- a/game/content/ghplots/mission_bigobs.py +++ b/game/content/ghplots/mission_bigobs.py @@ -325,6 +325,14 @@ class BAM_EscortShip(Plot): active = True scope = "LOCALE" + DIRECTION_BY_ANCHOR = { + pbge.randmaps.anchors.north: megaprops.SOUTHWARD, pbge.randmaps.anchors.northeast: megaprops.SOUTHWARD, + pbge.randmaps.anchors.east: megaprops.WESTWARD, pbge.randmaps.anchors.southeast: megaprops.WESTWARD, + pbge.randmaps.anchors.south: megaprops.NORTHWARD, pbge.randmaps.anchors.southwest: megaprops.NORTHWARD, + pbge.randmaps.anchors.west: megaprops.EASTWARD, pbge.randmaps.anchors.northwest: megaprops.EASTWARD, + pbge.randmaps.anchors.middle: megaprops.NORTHWARD, None: megaprops.SOUTHWARD + } + def custom_init(self, nart): myscene = self.elements["LOCALE"] allyfac = self.elements.get("ALLIED_FACTION") @@ -338,10 +346,12 @@ def custom_init(self, nart): self.register_element( "ESCORT_ROOM", ghrooms.OpenRoom(12, 12), dident=std_dident ) + entry_anchor = self.elements["ENTRANCE_ANCHOR"] + + my_facing = self.register_element("_facing", self.DIRECTION_BY_ANCHOR.get(entry_anchor, megaprops.EASTWARD)) myship = self.register_element("SHIP", megaprops.CivilianWaterShip(rank=self.rank), dident="ESCORT_ROOM") - entry_anchor = self.elements["ENTRANCE_ANCHOR"] troom = self.register_element( "TARGET_ROOM", ghrooms.IndicatedRoom( 12, 12, anchor=pbge.randmaps.anchors.OPPOSITE_EDGE[entry_anchor] @@ -357,17 +367,68 @@ def custom_init(self, nart): self.elements[CTE_REINFORCEMENT_ROOMS] = reinforcement_rooms team2: teams.Team = self.register_element( - CTE_ENEMY_TEAM, teams.Team(faction=myfac, enemies=(myscene.player_team,)) + CTE_ENEMY_TEAM, teams.Team(faction=myfac, enemies=(myscene.player_team, myship)) ) + self.register_element(CTE_INITIAL_ROOM, ghrooms.OpenRoom(10,10, anchor=pbge.randmaps.anchors.middle), + dident=std_dident) self.add_sub_plot(nart, CTHREAT_MECHA, ident="THREAT") self.did_init = False + self.obj = adventureseed.MissionObjective("Escort ship to safe zone", MAIN_OBJECTIVE_VALUE * 3) + self.adv.objectives.append(self.obj) + self.obj2 = adventureseed.MissionObjective("Protect ship from enemies", MAIN_OBJECTIVE_VALUE * 2) + self.adv.objectives.append(self.obj2) + + self.initial_health = 0 + return True def t_START(self, camp: gears.GearHeadCampaign): if not self.did_init: self.subplots["THREAT"].activate(camp) self.did_init = True + myprop = self.elements["SHIP"] + for p in myprop.get_members_in_play(camp): + self.initial_health += p.current_health + + def t_COMBATROUND(self, camp: gears.GearHeadCampaign): + myprop = self.elements["SHIP"] + if not myprop.get_members_in_play(camp): + pbge.alert("Ship Destroyed", font=pbge.HUGEFONT, justify=0) + self.obj.failed = True + camp.check_trigger("FORCE_EXIT") + else: + facing = self.elements["_facing"] + for t in range(5): + myprop.move(camp, *facing) + if self._ship_has_escaped(camp, myprop, facing): + pbge.alert("Ship has escaped!") + self.obj.win(camp) + self.obj2.win(camp, sum([p.current_health for p in myprop.get_members_in_play(camp)]) * 100 // self.initial_health) + camp.check_trigger("FORCE_EXIT") + break + + def _ship_has_escaped(self, camp: gears.GearHeadCampaign, ship: megaprops.MegaProp, facing): + troom = self.elements["TARGET_ROOM"] + all_out = True + for ship_part in ship.get_members_including_destroyed(camp): + if facing == megaprops.NORTHWARD: + if ship_part.pos[1] >= troom.area.bottom: + all_out = False + break + elif facing == megaprops.SOUTHWARD: + if ship_part.pos[1] < troom.area.top: + all_out = False + break + elif facing == megaprops.WESTWARD: + if ship_part.pos[0] >= troom.area.right: + all_out = False + break + else: + if ship_part.pos[0] < troom.area.left: + all_out = False + break + return all_out # ******************************* @@ -385,7 +446,9 @@ def t_START(self, camp: gears.GearHeadCampaign): CTE_MONSTER_TAGS = "CTE_MONSTER_TAGS" CTE_ENEMY_TEAM = "ENEMY_TEAM" +CTE_INITIAL_ROOM = "INITIAL_ENEMY_ROOM" # May be None, in which case starts without enemies. CTE_REINFORCEMENT_ROOMS = "REINFORCEMENT_ROOMS" +CTE_REINFOCEMENT_DELAY = "REINFORCEMENT_DELAY" # Defaults to a 1 round delay between reinforcements class CTMecha(Plot): LABEL = CTHREAT_MECHA @@ -398,6 +461,15 @@ def custom_init(self, nart): myroom = self.register_element("ROOM", roomtype(10, 10), dident="LOCALE") self.elements[CTE_REINFORCEMENT_ROOMS] = [myroom,] + if CTE_INITIAL_ROOM in self.elements and self.elements[CTE_INITIAL_ROOM]: + myteam = self.elements.get(CTE_ENEMY_TEAM, teams.Team(enemies=(self.elements["LOCALE"].player_team,), + faction=self.elements.get("ENEMY_FACTION"))) + myunit = gears.selector.RandomMechaUnit( + self.rank, 100, self.elements.get("ENEMY_FACTION"), self.elements["LOCALE"].environment, + add_commander=True + ) + myteam.deploy_in_room(self.elements["LOCALE"], self.elements[CTE_INITIAL_ROOM], myunit.mecha_list) + self.reinforcements_counter = 1 return True @@ -417,7 +489,7 @@ def deactivate(self, camp): def add_reinforcements(self, camp): myscene = self.elements["LOCALE"] myrooms = self.elements[CTE_REINFORCEMENT_ROOMS] - myunit = gears.selector.RandomMechaUnit(self.rank, 100, self.elements.get("ENEMY_FACTION"), + myunit = gears.selector.RandomMechaUnit(self.rank, 70, self.elements.get("ENEMY_FACTION"), camp.scene.environment, add_commander=False) mek1 = myunit.mecha_list[0] team2 = self.elements.get("ENEMY_TEAM", teams.Team(enemies=(myscene.player_team,))) @@ -429,3 +501,4 @@ def t_COMBATROUND(self, camp): self.reinforcements_counter -= 1 else: self.add_reinforcements(camp) + self.reinforcements_counter = self.elements.get(CTE_REINFOCEMENT_DELAY, 1) diff --git a/game/content/megaprops.py b/game/content/megaprops.py index bff30e56..cf82f77b 100644 --- a/game/content/megaprops.py +++ b/game/content/megaprops.py @@ -1,5 +1,7 @@ from game import teams import gears +import pbge +import random class MegaProp(teams.Team): POSITIONS = ((0,0), (1,0), (0,1), (-1,0), (0,-1)) @@ -29,6 +31,54 @@ def predeploy(self, gb, room): super().predeploy(gb, room) + def move(self, camp: gears.GearHeadCampaign, dx=0, dy=0): + # Locate all the parts of this prop. + prop_parts = [p for p in camp.scene.contents if camp.scene.local_teams.get(p) is self] + + # Locate all the positions where this prop is gonna be after the move. + destination_positions = {(p.pos[0] + dx, p.pos[1] + dy) for p in prop_parts} + blocked_tiles = camp.scene.get_blocked_tiles() + move_ok = True + collisions = list() + for actr in camp.scene.get_operational_actors(): + if hasattr(actr, "pos") and actr not in prop_parts and actr.pos in destination_positions: + candidates = [vec for vec in camp.scene.ANGDIR if (actr.pos[0] + vec[0], actr.pos[1] + vec[1]) not in blocked_tiles] + if candidates: + push_vec = random.choice(candidates) + pbge.my_state.view.anim_list.append( + pbge.scenes.animobs.MoveModel(actr, dest=(actr.pos[0] + push_vec[0], actr.pos[1] + push_vec[1])) + ) + blocked_tiles.add((actr.pos[0] + push_vec[0], actr.pos[1] + push_vec[1])) + collisions.append(actr) + else: + move_ok = False + collisions.append(actr) + + if move_ok: + for p in prop_parts: + pbge.my_state.view.anim_list.append(pbge.scenes.animobs.MoveModel(p, dest=(p.pos[0] + dx, p.pos[1] + dy))) + pbge.my_state.view.handle_anim_sequence() + #self.deal_with_collisions(camp, collisions) + else: + pbge.my_state.view.anim_list.clear() + self.deal_with_collisions(camp, collisions) + + def deal_with_collisions(self, camp, collisions): + invo = pbge.effects.Invocation( + name="Crash!!!", + fx=gears.geffects.DoDamage( + len(self.get_members_in_play(camp)), 10, + scatter=True, is_brutal=True, anim=gears.geffects.BigBoom + ), area=pbge.scenes.targetarea.SingleTarget(), + ) + invo.invoke(camp, None, [c.pos for c in collisions], pbge.my_state.view.anim_list) + + +NORTHWARD = (0,-1) +SOUTHWARD = (0,1) +EASTWARD = (1,0) +WESTWARD = (-1,0) + class CivilianWaterShip(MegaProp): POSITIONS = ((0,0), (0,1), (0,-1), (0,2), (0,-2)) diff --git a/game/teams.py b/game/teams.py index 11973947..03396455 100644 --- a/game/teams.py +++ b/game/teams.py @@ -62,3 +62,6 @@ def retreat(self, camp): def get_members_in_play(self, camp): return [npc for npc in camp.scene.contents if camp.scene.local_teams.get(npc, None) == self and npc.is_operational()] + + def get_members_including_destroyed(self, camp): + return [npc for npc in camp.scene.contents if camp.scene.local_teams.get(npc, None) == self] diff --git a/history.md b/history.md index f46f0092..831cc179 100644 --- a/history.md +++ b/history.md @@ -2,7 +2,7 @@ * Room contents with fixed positions will now be placed on map * Props are immovable even after they are destroyed * Props will not be placed somewhere they can block movement -* calc_mission_reward can automatically round the reward to an round number +* calc_mission_reward can automatically round the reward to a round number * Former lancemates who show up in buildings where they've been before will not be indicated as current members of the lance. * A captured territory must be consolidated before another territory can be invaded in WorldMapWar * PCs dying in a city scene should now be handled properly