Paul the Great's modding vertex

Not just a mere two-dimensional corner… n dimensions!

So apparently this is what all the cool kids are doing, and I don’t want to keep creating new threads for my creations, especially if they’re just tiny things.

So here’s the list, in brief!

  • UnitFramePlus: Adds bonus features to the unit frame, including a red border for hostile targets, a button for changing jobs, a button for toggling the Job activity on/off, and a group of buttons for attacking the selected hostile target with any or all military parties.
  • Obligation/Timer Tracker Tracks your consumable durations and trading stall cooldowns and allows you to directly use or auto-use them.
  • Insert Craft Order (Steam link) Adds an ugly button underneath the normal workshop craft button that inserts the order at the top of the queue instead of putting it at the bottom.
  • Town Inventory Tracker (Steam link) Extremely simple mod that adds click functionality to the items in the Town Overview Inventory tab: clicking on an item selects and moves the camera to it, and repeatedly clicking on the same entry cycles through all items collated there.
  • Storage Alert (storagealert.zip (5.1 KB)) Quick and dirty every-2-seconds-updating check on items that fail to be restocked. I don’t have any current plans to develop this, as I have a number of other higher priority things on my plate and I hate UI work, but it could be useful for finding items that aren’t getting restocked and figuring out why (reason isn’t included by the restock_director and I don’t want to try to hack it in).
  • What’s Stuck? Since everyone else is lazy and no one else did this, I finally got around to developing Storage Alert into a more usable mod that also alerts you when hearthlings get stuck. At some point I intend to add some support for finding what’s wrong with stuck buildings.
  • SH:TD - Stonehearth Tower Defense: A total conversion cooperative tower defense mod.

Right now I’m trying to puzzle my way through the water system (there’s a lot of code here! including the floating box mod and wet stone, it’s 41KB+3KB+20KB+34KB+44KB = 142KB of Lua, plus other relevant stuff) so I can make a couple entities that influence it.

5 Likes

All of my work these days is going into ACE - Authorized Community Expansion Project, but I can share what some of that work is.

I have successfully created toggleable water gates (enable/disable the flow of water through them) and pumps (displace water upwards, including into one another for stackable functionality). In this image you can see two pumps stacked on top of each other in a small pool (with a greater wet-stone immediately behind them), connected to a channel that passes through a gate that’s open so the water can go and get absorbed by the dry stones. The gate and pump models are just crates for now.

I also made an entity_modification_component that allows for dynamically changing certain entity settings including regions, either through passing a table of Cube3 or Region3 “objects”, passing a table in the style of a region specification in JSON, or (and this is for any of the supported setting types, including regions) just passing in a string key that you define in the JSON for your entity (in the entity_modification component section)! The only trouble I’m still having with it is that I don’t know how to deal with the C++ “boxed” region objects, so I can’t directly copy regions from other entities (only from their JSONs).

5 Likes

so thats what all that pump stuff was about! /aaaaah much hype/

The only trouble I’m still having with it is that I don’t know how to deal with the C++ “boxed” region objects

Pretty much all you need when working with boxed things is get(), which returns what’s in the box, and modify(fn) which takes the old value from the box and returns the new value to put into it (be it a modification of the input or something entirely different).

1 Like

And is there a better way for me to check if that’s what the input type was besides doing this?

if radiant.util.typename(r) == 'class radiant::dm::Boxed<class radiant::csg::Region<double,3>,1026>' then

I worry about trying to call :get() on something if that might not be a function it has. I guess I could also do a check to see if .get exists first.

Yeah, I would duck type it, if you want your API to be that permissive.

Is there any way to more specifically set a model variant than by the key (e.g., specify a file, another entity/render_info, or a key and index for “one_of” variants)? Based on testing, I suspect that when render_info_component loads arrays of model variants marked “one_of” it actually only loads in a single model chosen at that time for that entity. So being able to have a model variant randomly assigned by the game but also being able to change that variant later basically involves a lot of duplicate specification in the json? For example, I’d have to specify (in pseudo code here) "default": [ model1, model2, model3 ], "default_model1": [ model1 ], "default_model2": [ model2 ], "default_model3": [ model3 ].

I don’t think it’s a common use case or anything, I understand if that capability is intentionally limited or simply not intended, I just wanted to check in case I was missing something. Thanks for all your help!

I think keys are the only way to go.

I took a break from figuring out AI stuff last night (working on a non-hostile training dummy, I think I have it about half working and I’m pretty sure I know how to do the other half) to patch the town_patrol_service to consider manual_patrol entities as higher priority than auto_patrol entities: if you have any manual_patrol entities (e.g., a special crafted patrol banner), those are the only entities that will get patrolled (though currently it doesn’t force active patrollers to switch their patrol path right when you place one). If you remove them, it’ll go back to patrolling the auto_patrol entities.

2 Likes

My training dummy AI worked out fairly well after a lot of trial and error (it’s a lot trickier to debug AI than other things and/or I don’t know good strategies; the AI debug tool is nice, but it isn’t easy to query or call AI methods with the Lua console). Here are the basic rules for how the dummy functions:

  • it’s a normal, friendly, non-hostile entity
  • it has a lot of health (1000) and is repairable by the engineer
  • it will only be repaired while it’s “out of combat,” and getting used by trainers puts it “in combat” for a couple seconds
  • if it gets knocked down below 30% life, it will become disabled, requiring the engineer to repair it to full before it will be used for training again (in practice, attacks whose animations had already started will continue, so it’s possible to still destroy the dummy with many and/or very strong trainers)
  • all combat classes that equip a weapon (including the cleric’s healing tome) and are below maximum level are automatically eligible for training, but it can be disabled/enabled for eligible units by the player with a command for that unit
  • to train, a unit will approach the dummy’s front at an appropriate distance, taking into account weapon range, obstacles, and line of sight
  • if they have healing abilities, they’ll play a healing animation while doing nothing; otherwise, they’ll attack the dummy
  • after each attack/heal animation, they receive a small amount of experience

The other AI thing I was trying to do was getting pets to eat from storage, but I was having some trouble. My action doesn’t seem to get past an early stage of consideration, which I think it should, but I haven’t had the patience to continue on that yet.

For now I think I’m going to try adding terrain-grass-eating for pasture animals, which I think will be relatively straightforward.

2 Likes

Yeaaaaaaaaasshs
)(ooking forward to this one, good work on the dummy!)

1 Like

I have suddenly run into an issue where my monkey-patched component will call my overridden destroy() function, but only in the same game session as when it was created. If I save and reload, my version of the function will not be called. This is very disturbing to me because I’m monkey-patching a fair number of components in order to add the functionality I want, and overriding these functions is rather important.

Anyone have any ideas about what might be going on?

How do you intend to translate that grass eating thing to the snow and desert biome?

Plants, including grassy-type plants, still grow in those biomes (year-round, even!), so it’s mostly a matter of adjusting spawn/growth rates and making the color and possibly model variants match the biome.

Oh, so you are actually adding grass itself? I thought, you just meant the green cubes :smiley: Well, then it’s an entire different story. Way to go, dude, way to go :+1:

Thanks, I already got it working, just need to implement seasonal/weather effects and then someone else is making the models. I also got nearby water affecting crop growth rates, next step there is to extend it to wild/cultivated (non-farmed) plant life.

2 Likes

My latest additions to ACE’s codebase are a generic heatmap service (to replace the existing appeal heatmap) and a concept of “wilderness” that’s primarily used for modifying the spawn rate of trapping zones but can also be integrated into other entities (imagine a bird bath that, if there’s enough wilderness around it, periodically does an effect of birds or other animals coming up to it).

The heatmap service allows you to set up a heatmap by providing just a few settings and functions in a Lua file and adding the proper index to stonehearth_ace/services/client/heatmap/data/keys.json. The appeal heatmap was the first converted into the new system (plus a small fix for it being offset by 0.5 on the x axis for some reason), and naturally I made a wilderness one as well. The third example heatmap is for “ore detection:” it shows where ore veins are underground, with a brighter color for ore that’s closer to the surface or more numerous (my idea is that this will detect ore up to 10 voxels deep, or 20 if you’re playing as dwarves or have a particular town bonus, but of course that’s all subject to change). It does not show what type of ore, but it’s still a little cheaty because you can use it very effectively in slice mode (however, it doesn’t detect ore that you wouldn’t be able to see normally because it’s too far away; so it’s no more cheaty and rather simpler than preview-mining everywhere).

These latter two heatmaps are square rather than circular. For the wilderness one, this was important because I wanted the calculation to be very similar between the heatmap and the wilderness_signal_component which is used in trapping zones and is necessarily rectangular (it doesn’t just display in a square, it also looks at all entities within a rectangular region centered on the point [for the heatmap] or region [for the trapping zone] when calculating wilderness values). For the ore one, it just made sense because it’s a world of squares, and the ore veins are all rectangular.

This segues nicely into the concept of wilderness. The primary goal was to make trapping zone spawn rates more reflective of the area they’re in. In the base game, the only alteration that’s made to spawn rates is checking what the majority ground material is: if it’s “rock,” the spawn interval is between 70-100 in-game minutes; otherwise, it’s between 50-70 in-game minutes. The new wilderness_signal_component factors in dirt, grass, plants, and “animal”-kingdom critters (not pets, pasture animals, or farmer crops) as positives and buildings as negatives. “Mobile” critters are equipped with a separate wilderness_component that periodically checks to see if it’s within a wilderness signal region (or if it’s left one it was in before) and updates those signals accordingly. The wilderness_component can be attached to any entity to specify its wilderness value (and setting is_mobile to true will make it periodically check its location).

Raw default wilderness values are as follows: 5 for an animal, 1 per voxel in the collision or destination region of a plant, and -2 per voxel in the collision or destination region of a building. When a sampling region is passed to the wilderness_util.get_value_from_entity(...) function (as it is with the wilderness signal and heatmap), only the part of the building or plant region that intersects with the sampling region is counted. For both the wilderness signal and heatmap, the sampling region extends past the visible bounds of the region by a set amount to take into account nearby entities. Additionally, dirt terrain and grass terrain add 0.01 and 0.02 wilderness value, respectively, to a voxel. The total value of all the entities and terrain in a sampling region is then divided by the flat area of that region to come up with the actual wilderness value, which is then compared to a table of “tiers” similar to the tiers of appeal (and to the water affinity tables used by the water_signal_component and growing/evolving crops/plants).

In the future I hope to integrate humidity and temperature a little more mechanically with biomes, seasons, and weather, but in the shorter term I at least want to get biome-specific spawn rate modifiers implemented (e.g., you shouldn’t need the same number of big trees in the desert in order to attract the local wildlife as you need in the temperate forest). I already have a modifier for the type of animal you’re trying to trap (critters require less wilderness than normal, bugs require a normal amount, and big game* require more).

*Oh, did I forget to mention that I also added a level 6 tier of trapping grounds for big game? Currently only deer (and somewhat rarely “horned deer”) will spawn there, but we’re planning on adding bears, and perhaps wolves also.

4 Likes

the innter asterix and obelix lover in me needs to ask: any chance of wild boar for that large trapping grounds :stuck_out_tongue: ? the big variety? (inb4 monster hunter sized meals :stuck_out_tongue: )

2 Likes

Sure, not my department though. :stuck_out_tongue:

Check the top post for my new mod: What’s Stuck?

2 Likes