GHC v0.810: Black Market Blues

GHC_BlackMarketRules

GHCaramel v0.810 has just been released. A lot of the work this time around was under the hood- things have been refactored and updated to make future development (especially of the scenario generator) easier and less bug-prone. The big visible changes this time around are the new Black Market Blues scenario for DeadZone Drifter, the fact that you can edit your character’s appearance, gender, and name any time from the FieldHQ menu, and also the fact that gender is now completely customizable. I will note that Caves of Qud beat me at being the first Roguelike with fully customizable gender by a couple of weeks, but it’s kind of neat that we both did it in the same month. If I had known what Brian was planning I would’ve made a v0.805 release earlier.

GHC_CustomGender

You can download v0.810 from GitHub, itch.io, and Patreon.

GearHead Caramel v0.800: Not a Roadside Picnic

I’ve just released a new version of GearHead Caramel. The big change this time around is random world map encounters-instead of fighting the same old foes on each stretch of road, there are now a bunch of different events that could happen. Other changes include more characterization for lancemates and the ability to make some powerful enemies.

You can buy the game from itch.io, or download it for free from Patreon or GitHub.

GearHead Caramel Narrative Trailer

I have just finished making this narrative trailer for GearHead Caramel. All the backgrounds are hand painted with acrylics on watercolor paper; the mecha were drawn in Gimp and animated using Blender. I’d kind of like to choreograph and animate a decent mecha action scene someday… after I recover from the making of this video.

GearHead Caramel v0.700: Plastic Island Panic

The scenario card for Plastic Island Panic. A seaweed-covered Chameleon mecha is surfacing near Naugalon Island.

I’ve just uploaded a new release of GearHead Caramel to github and itch.io. The big change this time around is the scenario editor- you can now create your own adventures for GearHead Caramel without needing to know Python. And if you do know Python, you can edit your adventure to add stuff that the scenario editor can’t handle yet. This release also includes the test scenario I created with the editor, Plastic Island Panic. The blueprint file is included so you can try modifying the adventure yourself.

As usual, try it out and let me know what you think.

GearHead Caramel Scenario Creator

A picture of the GearHead Caramel scenario generator.

It’ll be easy, I thought. Just throw some widgets together to link boilerplate code and you’ll be finished in a week. I really need to stop listening to my stupid brain.

Anyhow, I’ve been working on a GearHead Caramel scenario editor! In theory, this should allow players to create scenarios for GearHead Caramel without knowing a lick of Python. It should also allow player-created scenarios to be distributed without the intrinsic security problems of distributing Python scripts. The next release of GearHead Caramel (hopefully soon…) will have an early alpha version of this editor. Everything seems to be working, and it is possible to create simple scenarios in it. The big obstacle for now is the interface, which just lists every adventure component in an unsorted tree view and is therefore not very user friendly.

As soon as the scenario I’m currently building via the editor is somewhat playable, I plan to make a release. If you want to try the editor before then you can download the source from GitHub and try from there.

GearHead Caramel Cinematic Trailer

GearHead Caramel now has a cinematic trailer, created by the mega-talented cartoonist and animator Matt Sandbrook.

GearHead Caramel v0.620: Content and Hotkeys

GearHead Caramel v0.620 has just been released. There are a lot of changes this time around- as you’d expect from the length of time it took me to release this version. Every town in DeadZone Drifter now has a special feature. The opening scenario for DeadZone Drifter may be different depending on your character’s life history. There’s a new pilot suit for all body types by LordErin. You can now set combat hotkeys for any weapon or action. Finally, some bugs were fixed and some improvements were made. Try it out and let me know what you think.

You can download the game from itch.io or GitHub.

GearHead: Caramel Coming to Steam, Possibly Relicensing GH1 + GH2

A picture of the GearHead Caramel character creation screen.

I have finished the paperwork and am preparing to release GearHead Caramel on Steam. I think I have everything set up correctly to compile the Linux binaries for SteamOS; we’ll see how it goes.

As I mentioned earlier, I would also like to bring GearHead1 and GearHead2 to Steam. To do this I’d need to change their license from LGPL to some other open source license, probably Apache 2.0 (which is what GearHead Caramel uses). This change would only affect future releases; existing versions will remain under LGPL.

Before changing the license I want to make sure that no contributors have any objections. Unfortunately, contacting contributors from 20 years ago and even figuring out who contributed what is proving to be difficult. So, if you have contributed to the GearHead code base and have any opinions about the license change, please let me know.

Modding GearHead Caramel: Create Your Own Adventure, Part Two

The Frigate Up repair shop in Namok.

In part one of the tutorial, we created an empty city scene and stuck an exit in it. In this part we will add a building, a shopkeeper, and a shop. You can download the expanded script file from Dropbox.

Now, we could stick all of this new content into the same Plot we used last time. However, I’ve found that it’s usually better to keep things in smaller, more manageable units. So we’re going to add a new Plot for the shop we’re going to add.

class FrigateUpRepairShop(Plot):
    LABEL = "POTES_REPAIR_SHOP"
    active = True
    scope = "LOCALE"

    def custom_init(self, nart):
        building = self.register_element("_EXTERIOR", game.content.ghterrain.ScrapIronBuilding(
            waypoints={"DOOR": ghwaypoints.GlassDoor(name="Frigate Up")},
            door_sign=(game.content.ghterrain.FixitShopSignEast, game.content.ghterrain.FixitShopSignSouth),
            tags=[pbge.randmaps.CITY_GRID_ROAD_OVERLAP]), dident="METROSCENE")

        team1 = teams.Team(name="Player Team")
        team2 = teams.Team(name="Civilian Team")
        intscene = gears.GearHeadScene(35, 35, "Wujung Tires", player_team=team1, civilian_team=team2,
                                       attributes=(gears.tags.SCENE_PUBLIC, gears.tags.SCENE_BUILDING, gears.tags.SCENE_SHOP),
                                       scale=gears.scale.HumanScale)

        intscenegen = pbge.randmaps.SceneGenerator(intscene, gharchitecture.ScrapIronWorkshop(),
                                                   decorate=gharchitecture.FactoryDecor())

        self.register_scene(nart, intscene, intscenegen, ident="LOCALE", dident="METROSCENE")

        foyer = self.register_element('_introom', pbge.randmaps.rooms.ClosedRoom(anchor=pbge.randmaps.anchors.south),
                                      dident="LOCALE")
        foyer.contents.append(ghwaypoints.MechEngTerminal())

        backroom = self.register_element('_backroom',
                                         pbge.randmaps.rooms.ClosedRoom(decorate=gharchitecture.StorageRoomDecor()),
                                         dident="LOCALE")

        game.content.plotutility.TownBuildingConnection(
            self, self.elements["METROSCENE"], intscene,
            room1=building, room2=foyer, door1=building.waypoints["DOOR"], move_door1=False
        )

        npc = self.register_element("SHOPKEEPER",
                                    gears.selector.random_character(50,
                                                                    local_tags=self.elements["METROSCENE"].attributes,
                                                                    job=gears.jobs.ALL_JOBS["Shopkeeper"]),
                                    dident="_introom")

        self.shop = services.Shop(services.MEXTRA_STORE, npc=npc, rank=50)

        return True

    def SHOPKEEPER_offers(self, camp):
        mylist = list()

        mylist.append(Offer("[OPENSHOP]",
                            context=ContextTag([context.OPEN_SHOP]), effect=self.shop,
                            data={"shop_name": "Frigate Up", "wares": "refurbished salvage"}
                            ))

        mylist.append(Offer("[HELLO] Frigate Up is the place you come when you've done just that.",
                            context=ContextTag([context.HELLO]),
                            ))

        return mylist

The attributes defined at the top of the new Plot class work the same as last time. The label needs to be unique for a given plot type, so for plots associated with “Pirates of the East Sea” I’m going to add a “POTES_” prefix. The scope of this Plot is set to “LOCALE”, which means that the scripts of this plot will only be called when the player is in the scene. I’ll talk about that a bit more when we get to the scripts.

The custom_init method is where we add things to the campaign world and set up for the adventure. The Plot we wrote last time created the city map and registered it under the element name “METROSCENE”. This new plot, which is going to be a child of the previous plot, will inherit all of these elements except the ones that begin with an underscore (_).

The first thing we add is a building to the city scene. The “dident” parameter of register_element is the Destination Identifier; we can use this parameter to place the element we’re registering inside of a previously defined element, in this case “METROSCENE”.

        building = self.register_element("_EXTERIOR", game.content.ghterrain.ScrapIronBuilding(
            waypoints={"DOOR": ghwaypoints.GlassDoor(name="Frigate Up")},
            door_sign=(game.content.ghterrain.FixitShopSignEast, game.content.ghterrain.FixitShopSignSouth),
            tags=[pbge.randmaps.CITY_GRID_ROAD_OVERLAP]), dident="METROSCENE")

That’s the building exterior taken care of. For the interior, we need to define a new scene. This works exactly the same as the city scene in the first tutorial.

        # Add the interior scene. Once again we have to define the teams, define the scene itself, and give it a
        # scene generator. Everything gets added to the campaign world using the register_scene method.
        team1 = teams.Team(name="Player Team")
        team2 = teams.Team(name="Civilian Team")
        intscene = gears.GearHeadScene(35, 35, "Frigate Up", player_team=team1, civilian_team=team2,
                                       attributes=(gears.tags.SCENE_PUBLIC, gears.tags.SCENE_BUILDING, gears.tags.SCENE_SHOP),
                                       scale=gears.scale.HumanScale)

        intscenegen = pbge.randmaps.SceneGenerator(intscene, gharchitecture.ScrapIronWorkshop(),
                                                   decorate=gharchitecture.FactoryDecor())

        # It's a convention for plots that add a scene to the adventure to label their primary scene "LOCALE".
        # This is not a rule, but it can be useful. Subplots which add things to a scene will usually use "LOCALE"
        # as the scene ID to add things to.
        self.register_scene(nart, intscene, intscenegen, ident="LOCALE", dident="METROSCENE")

Frigate Up is one of the repair shops in Namok in GearHead Arena. I’ve always liked that name, so that’s the repair shop we’re adding to our adventure. Our building is going to need at least one room, and while we’re at it we might as well add a second one.

        foyer = self.register_element('_introom', pbge.randmaps.rooms.ClosedRoom(anchor=pbge.randmaps.anchors.south),
                                      dident="LOCALE")

        # Let's add a back room for absolutely no reason.
        backroom = self.register_element('_backroom',
                                         pbge.randmaps.rooms.ClosedRoom(decorate=gharchitecture.StorageRoomDecor()),
                                         dident="LOCALE")

The two rooms will be automatically connected by the map generator. However, we’re going to have to connect the interior scene to the city exterior by ourselves. Fortunately the plotutility module provides some easy ways to do so.

        game.content.plotutility.TownBuildingConnection(
            self, self.elements["METROSCENE"], intscene,
            room1=building, room2=foyer, door1=building.waypoints["DOOR"], move_door1=False

The parameters here are the plot adding the connection (self), then the two scenes being connected. TownBuildingConnection could build a connection with just this information, but we want to specify a few more details. The exterior room is the ScrapIronBuilding and the interior room is the foyer. The exterior door is the door we added to the ScrapIronBuilding, and it should not be removed from where it is.

Mostly empty building

So now we have an exterior building, an interior scene, and we can move freely between the two. Time to add some content to the interior.

npc = self.register_element("SHOPKEEPER",
                                    gears.selector.random_character(50,
                                                                    local_tags=self.elements["METROSCENE"].attributes,
                                                                    job=gears.jobs.ALL_JOBS["Shopkeeper"]),
                                    dident="_introom")

        # Create the Shop service, and link it to this NPC.
        self.shop = services.Shop(services.MEXTRA_STORE, npc=npc, rank=50)

The shopkeeper is a random NPC with location tags taken from the city scene and job set to Shopkeeper. They will be placed in the foyer room.

The shop is an object of type services.Shop. This is a callable object, and so we can open the shop any time we like by calling self.shop(camp). In this case, we want the shop to be called from dialogue with the PC. So, we define a method that gives the shopkeeper some dialogue.

GearHead Caramel dialogue does not work like the dialogue in previous games. In fact, I’m not sure that any other game has ever used this method… but it’s a very useful method for a game that features procedural storytelling.

The terminology for dialogue in GHC is lifted directly from improv theatre, so if you’ve ever watched Whose Line Is It Anyway you are well on your way to understanding how it works. Instead of constructing a dialogue tree, you just have to supply a list of NPC Offers. An Offer is something that might be said by the NPC under certain circumstances. The player is then given a choice of several Replies to that Offer; each Reply leads to another of the NPC’s Offers, in GHC’s version of the Yes, And principle.

What this means is that an NPC’s dialogue can be constructed from multiple different sources, none of which need to know about each other, and you will still get a coherent conversation. The grammar that links Offers to Replies is found in the game.ghdialogue.ghreplies module.

The method that defines dialogue Offers for our shopkeeper is SHOPKEEPER_offers. Because the scope of this plot was set to LOCALE, these offers will only get added if the current scene is LOCALE. The first Offer in a friendly conversation will always have context HELLO; if the NPC doesn’t have a HELLO Offer defined, they can use a generic HELLO Offer. This Plot provides two Offers for our shopkeeper: one which opens the shop, and a HELLO Offer which gives the Frigate Up sales pitch.

    def SHOPKEEPER_offers(self, camp):
        mylist = list()

        mylist.append(Offer("[OPENSHOP]",
                            context=ContextTag([context.OPEN_SHOP]), effect=self.shop,
                            data={"shop_name": "Frigate Up", "wares": "refurbished salvage"}
                            ))

        # I am going to give the shopkeeper one more offer so they can give the Frigate Up sales pitch.
        mylist.append(Offer("[HELLO] Frigate Up is the place you come when you've done just that.",
                            context=ContextTag([context.HELLO]),
                            ))

        return mylist

Some things about the first Offer. The NPC’s dialogue is “[OPENSHOP]”; this is a grammar tag that will be expanded to a randomly generated line each time the shop is entered. This grammar tag needs some extra data, so the shop name and a brief description of its wares are included in the Offer. The effect is set to self.shop; this effect will be called when this Offer is given.

The second offer also uses a grammar tag, “[HELLO]”. You can see all of the standard grammar tags in the game.ghdialogue.ghgrammar module. It’s also possible for Plots to define custom grammar tags.

One last thing before our village has a fully functional shop; the FrigateUpRepairShop plot has to be added by the EastSeaPiratesScenario plot. This is accomplished by the following line in the EastSeaPiratesScenario custom_init method:

self.add_sub_plot(nart, "POTES_REPAIR_SHOP")

Download the script file from Dropbox, unzip it to the content folder, and play around with it. You can add different shops by copying and pasting the new Plot, then changing the parts you want. Let me know if you have any questions.

GearHead Discord

Once upon a time, in the distant past, there was a GearHead forum. Now, in the sparkling future, there is a GearHead Discord. You can use that link to join and talk about GearHead, mecha/games more generally, or just hang out. This is my first time starting a Discord and I have no idea what I’m doing.