diff --git a/data/stc_majornpcs.txt b/data/stc_majornpcs.txt index ad3bc301..44f9983a 100644 --- a/data/stc_majornpcs.txt +++ b/data/stc_majornpcs.txt @@ -189,4 +189,33 @@ Character mecha_colors=(Charcoal, Saffron, OrangeRed, SlateGrey, HeavyPurple) mecha_pref="SAN-D1 Daum" +Character + name = "Buma Yolos" + mnpcid = "BumaYolos" + statline = {Reflexes=22, Body=16, Speed=24, Perception=18, Knowledge=12, Craft=11, Ego=18, Charm=16} + colors = (HeavyPurple, MediumSkin, Beige, Saffron, PirateSunrise) + personality=[Shy, Easygoing, Glory, GreenZone] + portrait='card_m_jjangbogo.png' + gender=Female + job=MechaPilot + renown=80 + birth_year=128 + combatant=True + mecha_colors=(Charcoal, Saffron, OrangeRed, SlateGrey, HeavyPurple) + mecha_pref="SAN-D1 Daum" + +Character + name = "Carnelio" + mnpcid = "Carnelio" + statline = {Reflexes=15, Body=16, Speed=17, Perception=12, Knowledge=14, Craft=6, Ego=13, Charm=13} + colors = (HeavyPurple, MediumSkin, Beige, Saffron, PirateSunrise) + personality=[Cheerful, Sociable, Glory, L5DustyRing] + portrait='card_m_jjangbogo.png' + gender=Nonbinary + job=Pirate + renown=45 + birth_year=128 + combatant=True + mecha_colors=(Charcoal, Saffron, OrangeRed, SlateGrey, HeavyPurple) + mecha_pref="SAN-D1 Daum" diff --git a/game/content/__init__.py b/game/content/__init__.py index 0fe90bb9..eaa74aea 100644 --- a/game/content/__init__.py +++ b/game/content/__init__.py @@ -1,6 +1,5 @@ import collections -from pbge.plots import PlotError import pbge import gears from .. import exploration @@ -8,10 +7,10 @@ from . import ghterrain from . import ghwaypoints from . import ghcutscene -import uuid from . import backstory from . import ghrooms from . import ghchallenges +from . import megaprops # The list of plots will be stored as a dictionary based on label. diff --git a/game/content/ghplots/mission_bigobs.py b/game/content/ghplots/mission_bigobs.py index 1b1bf48f..d0604865 100644 --- a/game/content/ghplots/mission_bigobs.py +++ b/game/content/ghplots/mission_bigobs.py @@ -21,6 +21,7 @@ BAMO_BREAK_THROUGH = "BAMO_BREAK_THROUGH" BAMO_DEFEND_FORTRESS = "BAMO_DEFEND_FORTRESS" +BAMO_ESCORT_SHIP = "BAMO_ESCORT_SHIP" class BAM_ToTheOtherSide(Plot): LABEL = BAMO_BREAK_THROUGH @@ -316,3 +317,119 @@ def t_ENDCOMBAT(self, camp): if len(myteam.get_members_in_play(camp)) < 1: self.round_counter += 1 self._add_reinforcements(camp) + + +class BAM_EscortShip(Plot): + LABEL = BAMO_ESCORT_SHIP + active = True + scope = "LOCALE" + + def custom_init(self, nart): + myscene = self.elements["LOCALE"] + allyfac = self.elements.get("ALLIED_FACTION") + myfac = self.elements.get("ENEMY_FACTION") + entry_room: pbge.randmaps.rooms.Room = self.elements.get("ENTRANCE_ROOM") + entry_anchor = entry_room.anchor + + self.reinforcement_rooms = list() + for t, anc in enumerate(pbge.randmaps.anchors.EDGES): + if anc is not entry_anchor: + roomtype = self.elements["ARCHITECTURE"].get_a_room() + myroom = self.register_element("CORNER_ROOM_{}".format(t), roomtype(10, 10, anchor=anc), dident="LOCALE") + self.reinforcement_rooms.append(myroom) + + + team2: teams.Team = self.register_element("_eteam", teams.Team(faction=myfac, enemies=(myscene.player_team,))) + + myroom = self.register_element("CENTER_ROOM", + ghrooms.IndicatedRoom(16, 16, anchor=pbge.randmaps.anchors.middle), + dident="LOCALE") + + myroom2 = self.register_element("URBAN_ROOM", + ghrooms.MechaScaleResidentialArea(12, 12, anchor=pbge.randmaps.anchors.middle), + dident="CENTER_ROOM") + + bunker_team = self.register_element("_bunkerteam", teams.Team(faction=allyfac, enemies=(team2,), allies=(myscene.player_team,)), dident="URBAN_ROOM") + myfort = self.register_element("_bunker", gears.selector.generate_fortification(self.rank, myfac, myscene.environment)) + bunker_team.contents.append(myfort) + + self.round_counter = 0 + self.round_target = max(5, self.rank//10 + 2) + + self.obj0 = adventureseed.MissionObjective("Proceed to indicated area", 1) + self.adv.objectives.append(self.obj0) + self.obj = adventureseed.MissionObjective("Defend buildings for {} rounds".format(self.round_target), MAIN_OBJECTIVE_VALUE * 3) + self.adv.objectives.append(self.obj) + + self.init_ready = True + self.combat_started = False + + return True + + def t_START(self, camp: gears.GearHeadCampaign): + if self.init_ready: + myroom = self.elements["CENTER_ROOM"] + for x in range(myroom.area.x, myroom.area.x + myroom.area.width): + for y in range(myroom.area.y, myroom.area.y + myroom.area.height): + camp.scene.set_visible(x, y, True) + + self.init_ready = False + + def t_PCMOVE(self, camp: gears.GearHeadCampaign): + if not self.combat_started: + in_dest_zone = list() + outta_dest_zone = list() + dest_zone: pygame.Rect = self.elements["CENTER_ROOM"].area + for pc in camp.get_active_party(): + if dest_zone.collidepoint(*pc.pos): + in_dest_zone.append(pc) + else: + outta_dest_zone.append(pc) + # Remove mobility kills from the list of mecha that haven't made it to the end zone. They aren't making it. + # Because this check is expensive, only do it if at least some mecha are in the end zone. + if in_dest_zone: + for pc in list(outta_dest_zone): + if pc.get_current_speed() < 10: + pc.gear_up(camp.scene) + if pc.get_current_speed() < 10: + outta_dest_zone.remove(pc) + + if in_dest_zone and not outta_dest_zone: + self.obj0.win(camp, 100) + self.combat_started = True + self._add_reinforcements(camp) + + def _add_reinforcements(self, camp: gears.GearHeadCampaign): + myunit = gears.selector.RandomMechaUnit(self.rank, 75, self.elements.get("ENEMY_FACTION"), + camp.scene.environment, add_commander=False) + team2 = self.elements["_eteam"] + #print(myunit.mecha_list) + mek1 = myunit.mecha_list[0] + camp.scene.deploy_team(myunit.mecha_list, team2, random.choice(self.reinforcement_rooms).area) + game.combat.enter_combat(camp, mek1) + game.combat.enter_combat(camp, self.elements["_bunker"]) + + def t_COMBATROUND(self, camp): + if self.combat_started: + myteam = self.elements["_bunkerteam"] + if len(myteam.get_members_in_play(camp)) < 1: + pbge.alert("Buildings Destroyed".format(self.round_counter), font=pbge.HUGEFONT, justify=0) + self.obj.failed = True + camp.check_trigger("FORCE_EXIT") + + else: + self.round_counter += 1 + pbge.alert("Survived Round {}".format(self.round_counter), font=pbge.HUGEFONT, justify=0) + if self.round_counter >= self.round_target: + # Victory! + self.obj.win(camp, 100) + camp.check_trigger("FORCE_EXIT") + else: + self._add_reinforcements(camp) + + def t_ENDCOMBAT(self, camp): + if self.combat_started: + myteam = self.elements["_eteam"] + if len(myteam.get_members_in_play(camp)) < 1: + self.round_counter += 1 + self._add_reinforcements(camp) diff --git a/game/content/ghplots/ropp_main.py b/game/content/ghplots/ropp_main.py index d76afc69..f25a46f9 100644 --- a/game/content/ghplots/ropp_main.py +++ b/game/content/ghplots/ropp_main.py @@ -60,6 +60,7 @@ def custom_init(self, nart): WM_IMAGE_FILE="wm_map_piratespoint.png", WM_IDENTIFIER="WORLDMAP_6")) nart.camp.campdata['lets_get_tattoos'] = 0 + nart.camp.campdata['hero_points'] = 0 #:scenario_init self.build_world(nart) unique_id = "ropp" @@ -1042,6 +1043,14 @@ def build_world(self, nart): the_world['00000075'] = the_world['00000071'].entry_level the_world['00000078'] = the_world['00000071'].goal_level + + the_world['000000A2'] = pbge.randmaps.rooms.FuzzyRoom( + name='Engineering Room', anchor=None, decorate=None) + the_world['00000078'].contents.append(the_world['000000A2']) + + the_world['000000A3'] = ghwaypoints.OldMainframe( + name='Fusion Control Core', desc='', anchor=None) + the_world['000000A2'].contents.append(the_world['000000A3']) # Build the city here, store it in the_world team1 = teams.Team(name="Player Team") team2 = teams.Team(name="Civilian Team", allies=(team1, )) @@ -1435,12 +1444,15 @@ def build_world(self, nart): faction=None, attributes=[ gears.tags.SCENE_OUTDOORS, gears.tags.City, - personality.GreenZone, gears.tags.SCENE_PUBLIC + personality.GreenZone, gears.tags.SCENE_PUBLIC, + gears.tags.CITY_NAUTICAL ], exploration_music='Anthem of Rain - Adaptation (Instrumental).ogg', combat_music='HoliznaCC0 - Punk.ogg') # Create a scene generator - myroom = pbge.randmaps.rooms.Room(40, 25, anchor=pbge.randmaps.anchors.north) + myroom = pbge.randmaps.rooms.Room(40, + 25, + anchor=pbge.randmaps.anchors.north) myscenegen = pbge.randmaps.PartlyUrbanGenerator( myscene, game.content.gharchitecture.HumanScaleGreenzone( @@ -1528,6 +1540,31 @@ def build_world(self, nart): faction=None, combatant=True) the_world['00000084'].contents.append(the_world['00000089']) + splot = self.add_sub_plot( + nart, + "EMPTY_BUILDING", + elements=dict( + LOCALE=the_world['00000011'], + METROSCENE=the_world['00000011'], + METRO=the_world['00000011'].metrodat, + MISSION_GATE=the_world['00000013'], + CITY_COLORS=(gears.color.Burlywood, gears.color.FreedomBlue, + gears.color.LunarGrey, gears.color.Cobalt, + gears.color.OrangeRed), + INTERIOR_NAME='The House of Blades', + INTERIOR_TAGS=[ + gears.tags.SCENE_PUBLIC, gears.tags.SCENE_GARAGE, + gears.tags.SCENE_SHOP + ], + DOOR_SIGN=(ghterrain.BladesOfCrihnaSignEast, + ghterrain.BladesOfCrihnaSignSouth), + DOOR_TYPE=ghwaypoints.ScrapIronDoor, + INTERIOR_FACTION=gears.factions.BladesOfCrihna, + EXTERIOR_TERRSET=ghterrain.ScrapIronBuilding, + INTERIOR_ARCHITECTURE=gharchitecture.FortressBuilding, + INTERIOR_DECOR=gharchitecture.FactoryDecor())) + the_world['000000A7'] = splot.elements["INTERIOR"] + the_world['000000A8'] = splot.elements["FOYER"] # Build the city here, store it in the_world team1 = teams.Team(name="Player Team") team2 = teams.Team(name="Civilian Team", allies=(team1, )) @@ -3827,6 +3864,52 @@ def custom_init(self, nart): self.elements['LOCALE'] = nart.camp.campdata[THE_WORLD]['00000078'] element_alias_list = [('PARENT_SCENE', 'LOCALE')] + #: plot_init + #: plot_actions + + self.add_sub_plot(nart, + 'ROOM_ropp_177', + elements=dict([(a, self.elements[b]) + for a, b in element_alias_list]), + ident="") + return True + + #: plot_methods + + +class Room_ropp_177(Plot): + LABEL = "ROOM_ropp_177" + active = True + scope = "LOCALE" + + #: plot_properties + def custom_init(self, nart): + self.elements['ROOM'] = nart.camp.campdata[THE_WORLD]['000000A2'] + element_alias_list = [] + + #: plot_init + #: plot_actions + + self.add_sub_plot(nart, + 'WAYPOINT_ropp_178', + elements=dict([(a, self.elements[b]) + for a, b in element_alias_list]), + ident="") + return True + + #: plot_methods + + +class Waypoint_ropp_178(Plot): + LABEL = "WAYPOINT_ropp_178" + active = True + scope = "LOCALE" + + #: plot_properties + def custom_init(self, nart): + self.elements['WAYPOINT'] = nart.camp.campdata[THE_WORLD]['000000A3'] + element_alias_list = [] + #: plot_init #: plot_actions #: plot_subplots @@ -4396,8 +4479,14 @@ def custom_init(self, nart): elements=dict([(a, self.elements[b]) for a, b in element_alias_list]), ident="") + self.add_sub_plot(nart, + 'EMPTYBUILDING_ropp_180', + elements=dict([(a, self.elements[b]) + for a, b in element_alias_list]), + ident="") #: plot_actions - #: plot_subplots + + self.add_sub_plot(nart, 'ROPP_DOCKYARDS_PLOT') self.add_sub_plot(nart, 'CF_METROSCENE_RECOVERY_HANDLER', elements=dict([(a, self.elements[b]) @@ -4545,6 +4634,24 @@ def NPC_offers(self, camp): return mylist +class EmptyBuilding_ropp_180(Plot): + LABEL = "EMPTYBUILDING_ropp_180" + active = True + scope = "LOCALE" + + #: plot_properties + def custom_init(self, nart): + self.elements['ROOM'] = nart.camp.campdata[THE_WORLD]['000000A8'] + self.elements['LOCALE'] = nart.camp.campdata[THE_WORLD]['000000A7'] + element_alias_list = [('PARENT_SCENE', 'LOCALE')] + + #: plot_init + #: plot_actions + #: plot_subplots + return True + + + #: plot_methods class City_ropp_14(Plot): LABEL = "CITY_ropp_14" active = True diff --git a/game/content/ghplots/ropp_metroplots.py b/game/content/ghplots/ropp_metroplots.py index e69de29b..fef2c05b 100644 --- a/game/content/ghplots/ropp_metroplots.py +++ b/game/content/ghplots/ropp_metroplots.py @@ -0,0 +1,38 @@ +# City plots for Raid on Pirate's Point, since I am still working on the scenario editor. + +import random +from game import content, services, teams, ghdialogue +import gears +import pbge +from game.content import gharchitecture, plotutility, dungeonmaker, ghwaypoints, adventureseed, ghcutscene, ghterrain, \ + ghchallenges, ghrooms +from game.ghdialogue import context +from pbge.dialogue import Offer, ContextTag +from pbge.plots import Plot, Rumor, PlotState +from pbge.memos import Memo +from . import missionbuilder, rwme_objectives, campfeatures, worldmapwar, wmw_occupation +from pbge.challenges import Challenge, AutoOffer +from .shops_plus import get_building +import collections + + +class DockyardsPlot(Plot): + LABEL = "ROPP_DOCKYARDS_PLOT" + scope = "METRO" + active = True + + def custom_init(self, nart): + self.add_sub_plot(nart, "ROPP_DOCKYARDS_HOUSEOFBLADES") + return True + +class RoppDockHouseOfBlades(Plot): + LABEL = "ROPP_DOCKYARDS_HOUSEOFBLADES" + scope = "METRO" + active = True + + def custom_init(self, nart): + locale = self.register_element("LOCALE", nart.camp.campdata["SCENARIO_ELEMENT_UIDS"]['000000A7']) + room = self.register_element("_ROOM", nart.camp.campdata["SCENARIO_ELEMENT_UIDS"]['000000A8']) + + + return True diff --git a/game/content/ghterrain.py b/game/content/ghterrain.py index 75d7f0c2..41117219 100644 --- a/game/content/ghterrain.py +++ b/game/content/ghterrain.py @@ -686,6 +686,20 @@ class AlliedArmorSignEast(pbge.scenes.terrain.Terrain): image_top = 'terrain_decor_alliedarmor.png' +class BladesOfCrihnaSign(pbge.scenes.terrain.OnTheWallTerrain): + image_top = 'terrain_decor_bladesofcrihna.png' + + +class BladesOfCrihnaSignSouth(pbge.scenes.terrain.Terrain): + frame = 0 + image_top = 'terrain_decor_bladesofcrihna.png' + + +class BladesOfCrihnaSignEast(pbge.scenes.terrain.Terrain): + frame = 1 + image_top = 'terrain_decor_bladesofcrihna.png' + + class FixitShopSign(pbge.scenes.terrain.OnTheWallTerrain): image_top = 'terrain_decor_fixitsign.png' diff --git a/game/content/megaprops.py b/game/content/megaprops.py new file mode 100644 index 00000000..4270b008 --- /dev/null +++ b/game/content/megaprops.py @@ -0,0 +1,11 @@ +from game import teams +import gears + +class MegaProp(teams.Team): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def predeploy(self, gb, room): + for p in self.contents: + pass + diff --git a/game/exploration.py b/game/exploration.py index 3df7a7e6..214d222e 100644 --- a/game/exploration.py +++ b/game/exploration.py @@ -36,7 +36,7 @@ def __init__(self, explo, pos, party=None, dest_fun=None): pc = self.first_living_pc() # blocked_tiles = set( m.pos for m in explo.scene.contents ) self.pmm = self._get_party_mmode(explo) - self.path = scenes.pathfinding.AStarPath(explo.scene, pc.pos, pos, self.pmm) + self.path = scenes.pathfinding.AStarPath(explo.scene, pc.pos, pos, self.pmm, blocked_tiles=explo.scene.get_immovable_positions()) if self.EXCLUDE_LAST_STEP: self.path.results.pop(-1) #self.dest = self.path.results[-1] @@ -101,7 +101,9 @@ def move_pc(self, exp, pc, dest, first=False): else: move_ok = True for t in targets: - if not self.is_earlier_model(self.party, pc, t): + if t.IMMOVABLE: + move_ok = False + elif not self.is_earlier_model(self.party, pc, t): t.pos = pc.pos else: move_ok = False @@ -131,7 +133,7 @@ def __call__(self, exp): f_pos = pc.pos first = False elif not isinstance(pc, gears.base.Monster): - path = scenes.pathfinding.AStarPath(exp.scene, pc.pos, f_pos, self.pmm) + path = scenes.pathfinding.AStarPath(exp.scene, pc.pos, f_pos, self.pmm, blocked_tiles=exp.scene.get_immovable_positions()) for t in range(min(3, len(path.results) - 1)): self.move_pc(exp, pc, path.results[t + 1]) @@ -178,7 +180,7 @@ def __call__(self, exp): f_pos = self.npc.pos for pc in self.party: if pc.is_operational() and exp.scene.on_the_map(*pc.pos): - path = scenes.pathfinding.AStarPath(exp.scene, pc.pos, f_pos, self.pmm) + path = scenes.pathfinding.AStarPath(exp.scene, pc.pos, f_pos, self.pmm, blocked_tiles=exp.scene.get_immovable_positions()) if len(path.results) > 1: self.move_pc(exp, pc, path.results[1]) f_pos = pc.pos @@ -212,7 +214,7 @@ def __init__(self, explo, pc, pos, invo, target_list, record=False): """Move the pc to pos, then invoke the invocation.""" self.party = [pc, ] self.pos = pos - self.path = scenes.pathfinding.AStarPath(explo.scene, pc.pos, pos, self._get_party_mmode(explo)) + self.path = scenes.pathfinding.AStarPath(explo.scene, pc.pos, pos, self._get_party_mmode(explo), blocked_tiles=explo.scene.get_immovable_positions()) self.step = 0 self.record = record self.invo = invo @@ -240,7 +242,7 @@ def __call__(self, exp): f_pos = pc.pos first = False else: - path = scenes.pathfinding.AStarPath(exp.scene, pc.pos, f_pos, self._get_party_mmode(exp)) + path = scenes.pathfinding.AStarPath(exp.scene, pc.pos, f_pos, self._get_party_mmode(exp), blocked_tiles=exp.scene.get_immovable_positions()) for t in range(min(3, len(path.results) - 1)): self.move_pc(exp, pc, path.results[t + 1]) diff --git a/game/scenariocreator/statefinders.py b/game/scenariocreator/statefinders.py index 6f50fbf8..220d72c7 100644 --- a/game/scenariocreator/statefinders.py +++ b/game/scenariocreator/statefinders.py @@ -222,6 +222,7 @@ def __init__(self, name, desc, needed_data=()): "RegExLogoTerrain", "HospitalSign", "TownBanner", "GoldTownHallSign", "MilitarySign", "GeneralStoreSign1", "TavernSign1", "CafeSign1", "MechaModelSign", "SkullWallSign", "JollyRogerSign", "AegisLogoSign", "SolarNavyLogoSign", "DragonSign", "None", "KirasTattoosSign", "GunShopSign", "YeOldeShopSign", "ShieldSign", + "BladesOfCrihnaSign" ), "shop_type": ( "game.services.GENERAL_STORE", "game.services.GENERAL_STORE_PLUS_MECHA", "game.services.MECHA_STORE", diff --git a/gears/base.py b/gears/base.py index 038ec58b..18ca5240 100644 --- a/gears/base.py +++ b/gears/base.py @@ -4564,6 +4564,7 @@ class Prop(BaseGear, StandardDamageHandler, HasInfinitePower, Combatant): DEFAULT_MATERIAL = materials.Metal DODGE_SKILL = stats.MechaPiloting sort_priority = 1 + IMMOVABLE = True def __init__(self, statline=None, size=10, frame=0, destroyed_frame=1, action_points=3, **keywords): self.statline = collections.defaultdict(int) diff --git a/history.md b/history.md index 5cac7783..98d9271b 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,5 @@ +* 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 * 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 diff --git a/image/prop_block.png b/image/prop_block.png new file mode 100644 index 00000000..281474c3 Binary files /dev/null and b/image/prop_block.png differ diff --git a/image/terrain_decor_bladesofcrihna.png b/image/terrain_decor_bladesofcrihna.png new file mode 100644 index 00000000..1a3c94ba Binary files /dev/null and b/image/terrain_decor_bladesofcrihna.png differ diff --git a/pbge/randmaps/rooms.py b/pbge/randmaps/rooms.py index 3d0d0a24..f04d1b07 100644 --- a/pbge/randmaps/rooms.py +++ b/pbge/randmaps/rooms.py @@ -251,9 +251,24 @@ def deploy(self, gb, archi): good_walls.remove(p) i.place(gb, p) elif good_spots: - p = random.choice(good_spots) - good_spots.remove(p) - i.place(gb, p) + i.pos = None + if hasattr(i, "IMMOVABLE") and i.IMMOVABLE: + candidates = list(good_spots) + while candidates and not i.pos: + p = random.choice(candidates) + candidates.remove(p) + if gb.wall_wont_block(*p, mmode=archi.mmode): + i.place(gb, p) + good_spots.remove(p) + if not i.pos: + p = random.choice(good_spots) + good_spots.remove(p) + i.place(gb, p) + + else: + p = random.choice(good_spots) + good_spots.remove(p) + i.place(gb, p) def fill(self, gb, dest, floor=-1, wall=-1, decor=-1): # Fill the provided area with the provided terrain. diff --git a/pbge/scenes/__init__.py b/pbge/scenes/__init__.py index 39c4b4ad..20d15dc1 100644 --- a/pbge/scenes/__init__.py +++ b/pbge/scenes/__init__.py @@ -119,6 +119,7 @@ def place(self, scene, pos=None, team=None): frame = 0 altitude = None sort_priority = 0 + IMMOVABLE = False def get_sprite(self): """Generate the sprite for this thing.""" @@ -449,6 +450,9 @@ def clamp_pos(self, pos): nupos[1] = self.height-1 return tuple(nupos) + def get_immovable_positions(self): + return [thing.pos for thing in self.contents if hasattr(thing, "pos") and hasattr(thing, "IMMOVABLE") and thing.IMMOVABLE] + def __setstate__(self, state): # For saves from V0.921 or earlier, make sure there's wrap attributes. if "wrap_x" not in state: