Skip to content

Commit

Permalink
MultiMission appears to be working
Browse files Browse the repository at this point in the history
  • Loading branch information
jwvhewitt committed Aug 10, 2023
1 parent a939c96 commit 94ad902
Show file tree
Hide file tree
Showing 9 changed files with 413 additions and 53 deletions.
2 changes: 2 additions & 0 deletions credits.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Slightly Smiling Face, Beaming Face with Smiling Eyes by Emily Jäger
* Folder by Ines Simoes from the Noun Project
* Medicine by Braja Omar Justico from NounProject.com
* Vaccine by Anconer Design from NounProject.com
* Star by Adrien Coquet from Noun Project (CC BY 3.0)
* Skull by Brian Oppenlander from Noun Project (CC BY 3.0

MUSIC
=====
Expand Down
2 changes: 2 additions & 0 deletions game/content/ghplots/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from . import utility
from . import worldmapwar
from . import warplots
from . import wmw_battles
from game.content import PLOT_LIST, UNSORTED_PLOT_LIST
from pbge.plots import Plot

Expand Down Expand Up @@ -121,6 +122,7 @@ def harvest(mod):
harvest(utility)
harvest(worldmapwar)
harvest(warplots)
harvest(wmw_battles)

# Load the DLC.
import importlib.util
Expand Down
16 changes: 12 additions & 4 deletions game/content/ghplots/missionbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def __init__(self, camp, name, metroscene, return_wp, enemy_faction=None, allied
combat_music=None, exploration_music=None,
one_chance=True, data=None, win_message="", loss_message="", mission_grammar=None,
make_enemies=True, defeat_trigger_on=True, scale=gears.scale.MechaScale, desc="",
call_win_loss_funs_after_card=False,
**kwargs):
self.rank = rank or max(camp.pc.renown + 1, 10)
cms_pstate = pbge.plots.PlotState(adv=self, rank=self.rank)
Expand Down Expand Up @@ -165,6 +166,7 @@ def __init__(self, camp, name, metroscene, return_wp, enemy_faction=None, allied
self.environment = architecture.ENV

self.desc = desc
self.call_win_loss_funs_after_card = call_win_loss_funs_after_card

def copy(self):
mycopy: BuildAMissionSeed = copy.copy(self)
Expand All @@ -178,16 +180,22 @@ def copy(self):
def end_adventure(self, camp: gears.GearHeadCampaign):
# Update before ending, and again after.
camp.check_trigger("UPDATE")
if self.on_win and self.is_won():
self.on_win(camp)
elif self.on_loss and not self.is_won():
self.on_loss(camp)
if not self.call_win_loss_funs_after_card:
if self.on_win and self.is_won():
self.on_win(camp)
elif self.on_loss and not self.is_won():
self.on_loss(camp)
if self.is_won() and self.enemy_faction:
if self.defeat_trigger_on:
camp.check_trigger("DEFEAT", self.enemy_faction)
if self.make_enemies:
camp.set_faction_as_pc_enemy(self.enemy_faction)
super(BuildAMissionSeed, self).end_adventure(camp)
if self.call_win_loss_funs_after_card:
if self.on_win and self.is_won():
self.on_win(camp)
elif self.on_loss and not self.is_won():
self.on_loss(camp)
camp.time += 1

def can_do_mission(self, camp: gears.GearHeadCampaign):
Expand Down
237 changes: 237 additions & 0 deletions game/content/ghplots/multimission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import pbge
from pbge.plots import Plot
import gears
from . import missionbuilder
from game.content import gharchitecture, plotutility, dungeonmaker, ghwaypoints, adventureseed, ghcutscene, ghterrain


def end_mission_lance_recovery(camp):
creds = camp.totally_restore_party()
if creds > 0:
pbge.alert("Repair/Reload: ${}".format(creds))
camp.credits -= creds
camp.time += 1


class MultiMissionNodePlot(Plot):
# Contains one mission or other feature of the multi-mission.
# Required Elements:
# WIN_MISSION_FUN, LOSE_MISSION_FUN: Callables to call in the event of a win or loss.
# ONE_SHOT: If True, the mission automatically advances with no rest in between. Defaults to True.
# Optional Elements:
# ENEMY_FACTION, ALLIED_FACTION
# COMBAT_MUSIC, EXPLORATION_MUSIC
# MISSION_DATA
# MISSION_GRAMMAR
active = True
scope = True

NAME_PATTERN = ""
OBJECTIVES = (missionbuilder.BAMO_LOCATE_ENEMY_FORCES, missionbuilder.BAMO_CAPTURE_BUILDINGS)

def custom_init(self, nart):
if "SCENE_GENERATOR" not in self.elements or "ARCHITECTURE" not in self.elements:
sgen, archi = gharchitecture.get_mecha_encounter_scenegen_and_architecture(self.elements["METROSCENE"])
self.elements["SCENE_GENERATOR"] = sgen
self.elements["ARCHITECTURE"] = archi
return True

def create_mission(self, camp):
return missionbuilder.BuildAMissionSeed(
camp, self.NAME_PATTERN.format(**self.elements),
self.elements["METROSCENE"], self.elements["MISSION_GATE"],
self.elements.get("ENEMY_FACTION"), self.elements.get("ALLIED_FACTION"), self.rank,
self.OBJECTIVES,
custom_elements=self.elements.get("CUSTOM_ELEMENTS", None),
scenegen=self.elements["SCENE_GENERATOR"], architecture=self.elements["ARCHITECTURE"],
on_win=self._on_win, on_loss=self._on_loss,
combat_music=self.elements.get("COMBAT_MUSIC"), exploration_music=self.elements.get("EXPLORATION_MUSIC"),
data=self.elements.get("MISSION_DATA", {}),
mission_grammar=self.elements.get("MISSION_GRAMMAR", {}),
restore_at_end=not self.elements.get("ONE_SHOT", True),
call_win_loss_funs_after_card=True
)

@property
def name(self):
return self.NAME_PATTERN.format(**self.elements)

def _on_win(self, camp: gears.GearHeadCampaign):
myfun = self.elements.get("WIN_MISSION_FUN", None)
if myfun:
myfun(camp)

def _on_loss(self, camp: gears.GearHeadCampaign):
myfun = self.elements.get("LOSE_MISSION_FUN", None)
if myfun:
myfun(camp)

def call_node(self, camp):
mymission = self.create_mission(camp)
mymission(camp)

def can_do_mission(self, camp):
return bool(camp.get_usable_party(gears.scale.MechaScale, just_checking=True,
enviro=self.elements["ARCHITECTURE"].ENV))


class MultiMissionMenu(pbge.rpgmenu.Menu):
WIDTH = 350
HEIGHT = 250
MENU_HEIGHT = 75

FULL_RECT = pbge.frects.Frect(-175, -75, 350, 250)
TEXT_RECT = pbge.frects.Frect(-175, -75, 350, 165)
FRAME_WIDTH = 64

def __init__(self, mmission, desc):
self.mmission = mmission
super().__init__(
-self.WIDTH // 2, self.HEIGHT // 2 - self.MENU_HEIGHT + 75, self.WIDTH, self.MENU_HEIGHT, predraw=self.pre,
no_escape=True, border=None, font=pbge.MEDIUMFONT
)
self.desc = desc
self.img = pbge.image.Image("sys_multimissionstages.png", self.FRAME_WIDTH, self.FRAME_WIDTH)

sdrw = (len(mmission.mission_stages) * 2 - 1) * self.FRAME_WIDTH
self.stages_display_rect = pbge.frects.Frect(-sdrw//2, -175, sdrw, self.FRAME_WIDTH)

def pre(self):
if pbge.my_state.view:
pbge.my_state.view()
pbge.default_border.render(self.FULL_RECT.get_rect())

mydest = self.stages_display_rect.get_rect()
pbge.default_border.render(mydest)
num_stages = len(self.mmission.mission_stages)
for t in range(num_stages):
t_stage = self.mmission.mission_stages[t]
myframe = t_stage.stage_frame
if t < self.mmission.mission_number:
myframe += 10
elif t > self.mmission.mission_number:
myframe += 5
self.img.render(mydest, myframe)
mydest.x += self.FRAME_WIDTH
if (t+1) < num_stages:
self.img.render(mydest, 15)
mydest.x += self.FRAME_WIDTH

pbge.draw_text(pbge.my_state.medium_font, self.desc, self.TEXT_RECT.get_rect(), justify=0)



class MultiMissionStagePlot(Plot):
# This plot describes a stage of the multi-mission. It will contain one or more nodes, which are usually individual
# missions but hey feel free to go nuts.
# Required Elements:
# WIN_MISSION_FUN, LOSE_MISSION_FUN: Get passed on to the nodes.
# ONE_SHOT: If True, the mission automatically advances with no rest in between. Defaults to True.
# Optional Elements:
# COMBAT_MUSIC, EXPLORATION_MUSIC
# MISSION_DATA
# MISSION_GRAMMAR
active = True
scope = True

DESC_PATTERN = ""

STAGE_NORMAL = 0
STAGE_HARD = 1
STAGE_RESTORE = 2
STAGE_BOSS = 3
STAGE_CONCLUSION = 4
stage_frame = STAGE_NORMAL

def custom_init(self, nart):
self.nodes = list()
self._build_stage(nart)
return True

def _build_stage(self, nart):
raise NotImplementedError("No build stage method declared for {}".format(self.LABEL))

def _add_stage_node(self, nart, nodelabel: str, necessary: bool = True):
mynode = self.add_sub_plot(nart, nodelabel, necessary=necessary)
self.nodes.append(mynode)

def _get_stage_desc(self, camp):
return self.DESC_PATTERN.format(**self.elements)

def _create_node_menu(self, camp, mmission):
mymenu = MultiMissionMenu(mmission, self._get_stage_desc(camp))
for node in self.nodes:
if node.can_do_mission(camp):
mymenu.add_item(node.name, node.call_node)
mymenu.sort()
if mmission.mission_number > 0:
mymenu.add_item("Abort the mission", None)
return mymenu

def call_stage(self, camp, mmission):
mymenu = self._create_node_menu(camp, mmission)
dest = mymenu.query()
if dest:
dest(camp)
elif mmission.elements.get("ONE_SHOT", True):
print("Losing mission!")
mmission._lose_mission(camp)


class MultiMission(Plot):
# Required Elements:
# METROSCENE, MISSION_GATE, WIN_FUN, LOSS_FUN
# ONE_SHOT: If True, the mission automatically advances with no rest in between. Defaults to True.
# Optional Elements:
# COMBAT_MUSIC, EXPLORATION_MUSIC
# MISSION_GRAMMAR
active = True
scope = True

mission_stages = None
mission_number = 0

def custom_init(self, nart):
self.mission_stages = list()
self.mission_number = 0
self.elements["WIN_MISSION_FUN"] = self._win_mission
self.elements["LOSE_MISSION_FUN"] = self._lose_mission
self._build_mission_graph(nart)
return True

def _win_mission(self, camp: gears.GearHeadCampaign):
self.mission_number += 1
if self.mission_number >= len(self.mission_stages):
myfun = self.elements.get("WIN_FUN", None)
if myfun:
myfun(camp)

self.end_plot(camp, True)

else:
if self.elements.get("ONE_SHOT", True):
self(camp)

def _lose_mission(self, camp: gears.GearHeadCampaign):
if self.elements.get("ONE_SHOT", True):
# The player has failed this multi-mission. End now.
end_mission_lance_recovery(camp)
myfun = self.elements.get("LOSS_FUN", None)
if myfun:
myfun(camp)
self.end_plot(camp, True)

def _build_mission_graph(self, nart):
raise NotImplementedError("No build mission graph method declared for {}".format(self.LABEL))

def _add_stage(self, nart, stagelabel: str, necessary: bool = True):
mynode = self.add_sub_plot(nart, stagelabel, necessary=necessary)
self.mission_stages.append(mynode)

def can_do_mission(self, camp):
# Defaults to True, feel free to change this one.
return True

def __call__(self, camp):
# Display the menu, go to the next encounter as appropriate.
self.mission_stages[self.mission_number].call_stage(camp, self)
90 changes: 90 additions & 0 deletions game/content/ghplots/wmw_battles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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, multimission
from pbge.challenges import Challenge, AutoOffer
from .shops_plus import get_building
import collections

class WMWMission(multimission.MultiMission):
# Required Elements:
# METROSCENE, MISSION_GATE, WIN_FUN, LOSS_FUN
# TARGET_GATE, TARGET_SCENE
# ENEMY_FACTION, ALLIED_FACTION
# NUM_STAGES: The number of stages for this battle. Should be between 2 (hard) and 5 (hubris-destroying).
# Optional Elements:
# COMBAT_MUSIC, EXPLORATION_MUSIC
# MISSION_GRAMMAR
LABEL = "WMW_MISSION"

def custom_init(self, nart):
sgen, archi = gharchitecture.get_mecha_encounter_scenegen_and_architecture(self.elements["TARGET_SCENE"])
self.elements["SCENE_GENERATOR"] = sgen
self.elements["ARCHITECTURE"] = archi
return super().custom_init(nart)

def _build_mission_graph(self, nart):
num_stages = self.elements.get("NUM_STAGES", 3)
recovery_time = -25
hard_battle = 0
while num_stages > 1:
if random.randint(1,100) <= recovery_time:
self._add_stage(nart, "WMW_BATTLE_STAGE")
recovery_time = -25
hard_battle += 30
elif random.randint(1,100) <= hard_battle:
self._add_stage(nart, "WMW_BATTLE_STAGE")
hard_battle = 0
else:
self._add_stage(nart, "WMW_BATTLE_STAGE")
hard_battle += 10
recovery_time += 50
num_stages -= 1

self._add_stage(nart, "WMW_BATTLE_STAGE")

def can_do_mission(self, camp):
return bool(camp.get_usable_party(gears.scale.MechaScale, just_checking=True,
enviro=self.elements["ARCHITECTURE"].ENV))


class WMWBattleStage(multimission.MultiMissionStagePlot):
LABEL = "WMW_BATTLE_STAGE"

DESC_PATTERNS = (
"You need to battle through the forces of {ENEMY_FACTION} to reach your destination.",
"The battle to capture {TARGET_SCENE} from {ENEMY_FACTION} will be a long one."
)

def _build_stage(self, nart):
self.DESC_PATTERN = random.choice(self.DESC_PATTERNS)
self._add_stage_node(nart, "WMW_BATTLE_NODE")


class WMWDefaultBattleNode(multimission.MultiMissionNodePlot):
LABEL = "WMW_BATTLE_NODE"
NAME_PATTERN = "Capture territory held by {ENEMY_FACTION}"

def create_mission(self, camp):
return missionbuilder.BuildAMissionSeed(
camp, self.NAME_PATTERN.format(**self.elements),
self.elements["METROSCENE"], self.elements["MISSION_GATE"],
self.elements.get("ENEMY_FACTION"), self.elements.get("ALLIED_FACTION"), self.rank,
self.OBJECTIVES,
custom_elements=self.elements.get("CUSTOM_ELEMENTS", None),
scenegen=self.elements["SCENE_GENERATOR"], architecture=self.elements["ARCHITECTURE"],
on_win=self._on_win, on_loss=self._on_loss,
combat_music=self.elements.get("COMBAT_MUSIC"), exploration_music=self.elements.get("EXPLORATION_MUSIC"),
data=self.elements.get("MISSION_DATA", {}),
mission_grammar=self.elements.get("MISSION_GRAMMAR", {}),
restore_at_end=False, auto_exit=True,
call_win_loss_funs_after_card=True
)

Loading

0 comments on commit 94ad902

Please sign in to comment.