News:

Please request registration email again and then check your "Spam" folder

Former www.henthighschool.com

Dev Blog

Started by Shilo, May 15, 2022, 09:44 AM

Previous topic - Next topic

ShiloTopic starter

(Originally posted in the old forum on April 05, 2020)

Hi all!

Some of you have voiced their concern about the lack of progress updates. After all, the last version 1.9.5 was released more than 1.5 years ago. And while the next release is still some time off, I want to use this thread to provide you with some insight into the changes we've been making in the internal development build.

I'll keep the thread locked to prevent cluttering with other comments, so future updates can be found more easily. And I'll also not write up everything at once. Mostly because it takes me forever to write stuff. But also for teasing. ;)

But without further ado, let's start this blog with the first new thing:

UI Notifications

Probably the new engine feature that is most noticeable for players in the next update. Up until now, the game would only inform you via colored floating numbers about changes to the global average stats of your students. However, this system has several limitations:

  • Since it only shows changes to the global average, it fails to properly communicate the effects of the event that is currently playing out on the affected NPCs. The more students you have in your school, the less impact the individual changes of an event will have on the average value.
  • The average value may also misrepresent the effects an event may have on an individual if there is another group that is affected in the opposite direction. Example: You could have an event where you are making fun of a single student in class. While the other students may laugh about that and raise the overall happiness, the one student you are making fun of may actually have his happiness lowered significantly. But that effect would not be visible if you just looked at the resulting average.
  • Because the changes were only shown when time passed, you may not even see the results of events that take place without time progression until you encounter a different, unrelated event. This also causes the results to be mixed up with stat changes that occur naturally, like school subject influence and the population's mind adjustment.


To solve this problem, we added support for several event operations to report changes right when they occur.
However, displaying each change to each stat for every NPC and every operation individually would lead to a copious amount of notifications (which would keep going as floating numbers for several minutes even after the event ended). So the system also includes several mechanics to reduce the noise:

  • Instead of immediately showing the notifications when an operation is performed, the change is first recorded into an internal list. It is then only shown if either the event finishes or by triggering a new operation inside the event that explicitly starts displaying the gathered notifications. This makes it possible to accumulate changes to the same stat of the same NPC throughout the event, even if it happens via multiple operations. But if the event author wants to display them in a timely manner, they are still free to do so.
  • There is also a new operation to assign NPCs to a named "Notification Group". When displaying the gathered notifications, all individual stat changes for all NPCs in a group are averaged and only the average is shown as notification, with the name of the Group being used as label. So in the example from before, you could have individual notifications for the student you made fun of (which show up with his name) and then put the entire rest of the class into a Notification Group named "Laughing Students", which would then show the average change for all of them along with the number of NPCs currently in that group. The Notification Groups can also be useful if you want to hide an NPC's true name for story reasons and avoid giving the name away via notification label. Just put that NPC into a group with the fake name you want him to have. The group assignment only persist for the duration of the event's execution, but are passed down to Remote Event executions.
  • For convenience and as safeguard, NPCs who have their stats changed via the "Person Stats by List" operation are automatically considered part of a group (which will be displayed with a name like "Persons"), unless they are already part of an existing group. It is generally preferable to name your groups explicitly at the start of an event execution.
  • It is also possible to define which stats are recorded for notifications under which circumstances. We don't need to keep track of Energy notifications because the player already has a big UI element that shows that value at all times, and the stat is not relevant for NPCs. And we don't need to keep track of Loyalty and Lust for the player character because these stats have no use for him. Likewise the Grunge stat is only used by the player, so we don't need to track it for NPCs.


This new feature has only just recently been added to the engine and we are still in the process of updating events to make proper use of it, since adding the Notification Groups to avoid flooding with messages is a manual task. Which makes this one of the reasons why the game is currently not in a releasable state.
The global stat notifications still exist, of course. This new system is just a more fine-grained variant.

Notifications are currently supported for stats, skills, and body sizes. I am also considering to track changes to the Relationship value in the database, if it does not affect performance too much. Displaying entirely custom notifications via VEE operation may be planned in the future but is currently out of scope. Spinner has also requested some more operations and adjustments, which I'll provide in due time.

While it may not seem like a spectacular feature at first, we think it really adds a feeling of agency and meaning to events and your decisions: Whether you praised a student, scolded them or sent them to detention barely seemed to make any difference. But now the consequences of your choice on that student are immediately made known by showing you the changes in their loyalty, happiness and corruption.

ShiloTopic starter

#1
(Originally posted in the old forum on April 07, 2020)

Global Variables

Global Variables are a new type of variable in the VEE and are a step towards readying the game for modding support. They are available for all primitive variable types: Integer, Double, Boolean, String and Date and can be quickly created by converting their non-global counterparts.

In order to explain why I consider them necessary for proper modding support, I need go a bit into how I plan the modding system to work:

In the game's current state, mods only work by adding their files directly in the game's directory, potentially replacing files from the official release version. With the modding framework, each mod should reside in its own directory next to the official release files. Via a mod configuration interface within the game's main menu can you then choose which of these individual directories (in other words: mods) should be included in the game you are about to start, as well as the order in which they are included.
Whenever the game engine then needs to lookup something in the file system, it will also go through the list of included directories and check if any of them have resources with the same relative path (from its respective directory root) as the resource that was requested. If the path matches one of a resource further up in the ordered directory list (including the base game), it will be used instead of the original one.
In cases where the game engine wants to access everything in a specific subdirectory (for example to show a random image from a folder), it will simply add the resources from the mod to the existing one if they are in the same relative subdirectory.

However, simply replacing existing events in their entirety with custom ones could lead to problems because of the concept of Variable References that we have been using until now. Variable References allow us to use variables defined in other events by referencing them via the event's relative file path and the ID of the variable inside the event.
Most of the quests in the game have a variable in the first event of the quest line that keeps track of the current progress in that quest. All subsequent events of the quest line then reference that single statekeeping variable. But if a mod decided to replace that original starting event with a completely different one, where the statekeeping variable did not happen to have the same ID as the original one, then all other events of the quest line would no longer be able to reference that variable and would break.

This is where Global Variables come into play. Unlike regular variables, the values of Global Variables are not stored in the VEE variable itself. Instead, the data containers for Global Variables are defined by individual XML files where each container is given a unique name, data type, optional description and default value. These containers are then stored in a separate list by the game engine, where the VEE proxy variables can simply reference them by their unique name. So instead of having Variable References that point to a single statekeeping variable in an event, you would use Global Variable proxies instead to point to a named shared container that is stored outside of any events.

And while the initial definition of each Global Variable in a separate XML file is a bit cumbersome, they add additional clarity and intent to event authoring by making you explicitly document which variables are intended to be used across multiple events.

The corresponding VEE menu will show you a list of all defined Global Variables containers to which you can bind the proxy variable. A full list of all Global Variables is also available in a new tab in the debug menu, where you can also modify their current value. This makes it easier to debug quest progression (and to cheat, but probably also to screw things up – use at your own risk).

Variable References will of course continue to work and still have their place to link to variables within the same event for the sake of avoiding VEE clutter. It just means that events that reference variables from other events are not guarded against breaking from mods. We won't rework the existing events right away, but perhaps as the need arises.

ShiloTopic starter

#2
(Originally posted in the old forum on July 12, 2020)

Error Checking Revamp

This feature is just a minor one and is primarily of consequence to developers, though players may benefit from less bugs as a result.

When the editor was checking events for structural errors, the old version was simply reporting any findings as individual lines of text in a console window.

A major drawback of that system was that it required to provide all necessary information in the error message, for example the ID and name of the operation that caused the issue. That error message is custom-made for each of the countless operations we have in the engine, though, and not all of them were always providing that information. It also made the process of adding just one more piece of information (the name of the containing event) a whole lot of work.

But it had to be done at some point, so I went in and reworked the entire system with all the error messages and changed it to produce a list of structured data, rather than just a single block of text. The structured data now includes stuff like the name of the operation, its ID, the containing event file and a severity level. Some of that information was previously contained in the error message itself, which has been shortened now as a result.

Why do all that in the first place?
Aside from being able to display errors in a more unified and structured manner, having the name of the containing event is also important: we now also have a new option in the Dev Tools menu that lets us check all events for errors at once, and all results are collected in a single table. This makes it much easier to fix any issues that would otherwise pop up during gameplay. Previously we would have needed to open and check each event individually, which is not really feasible with the more than 1400 event files that we currently have in the game. As you can see from the screenshot, we still have some cleaning up to do before the release.

Another benefit: while the old system would only report actual errors, the new system also has a severity associated with the message and can be used to report informative stuff or warnings that are not necessarily errors. There is also the severity level "autofixed", which is used when the error checking process caused the operation to automatically repair itself.
And in case anyone wants to copy-paste the errors like they were able to do with the console version: the table output can also be exported as CSV file.

ShiloTopic starter

(Originally posted in the old forum on July 13, 2020)

The little things

The previous blog entries went into the details of some new systems because they are elaborate enough to warrant individual posts. But we should not forget about the small changes that may not seem like much on their own but hopefully help to improve the overall experience both for developers and players.

This post will only cover things that changed in the engine itself, and not the numerous improvements on the content side. It's also just a simple rundown of the commits that have taken place, so don't expect too much fleshed out details.

Let's start with a slightly more noticeable change:
Showing decision options to the player now supports Font Awesome at the beginning of the text. To do this, simply have the decision start with the name of the icon, for example "fa-book". It will then be replaced by the actual icon when displaying it. An example where we use this is the revamped Smartphone (see the attached screenshot). Previously the use of the icons was limited to Button Events and UI buttons.


There is an ongoing effort to decrease the number of hardcoded "magic numbers" in the engine and make more stuff configurable, to allow for easier modding and use in other scenarios in the future. The following changes play into that:

- DetentionLocationProvider will now use the first rule with "detention" (case-insensitive) in its name instead of looking hard-coded for "Detention rules".
- More population-related properties can now be configured in the Scenario Config.
- The maximum number of students per class can now be configured via the Scenario Config. A new VEE constant is available that returns the current value of the config.
- ScenarioConfig is now visible in the Game Objects debug tab, so properties can be easily checked and changed during ongoing play.


Some new VEE operations or new variants of existing ones have been added as well.

- "Item by Name" operations now use the regular "Name" property of an item by default and have a link variation for the "Display Name".
- "Set Rule Active" is now named "Set Rule Available" in code and XML.
- "Set Rule Choice Active" is now named "Set Rule Choice Available" in code and XML.
- Added a new operation "Set Rule Choice Active" (i.e. same name as the old operation) that can be used to set the currently active Rule Choice from VEE.
- Added "Get Rule" operation to acquire a Rule object for use with reflective property modification.
- Removed "Check Favor Indicator" condition. Favor indicators are handled entirely with Status Flags from Status Effects now, so there is no need to have a dedicated operation for the old stuff.
- Removed "Person Has Birthday" condition. The same can be achieved by getting the actual birthday date and some simple comparison operations. There is also a Function Library event that handles this.


And then there are the things we changed because it just seemed better that way. Some of these may require modders to make small adjustments to their mods when we release the new version, though.

- Rules now apply the actual value for a Reputation change on each day. Previously, it would only apply 1/5th of the value to avoid too rapid growth. This behavior was confusing and entirely opaque to modders.
- CurrentOutfit is now always calculated whenever its checked.
- While showing a teacher in the management panel, their OutfitName will be set to "Work" while updating their paperdoll and back to the previous value afterwards.
- Adding/Setting the relationship value in the database now clamps it to the [-100;100] range. Previously it was only clamped when reading the value back to code, which could give you values outside that range when querying the database directly.
- Clubs will now validate if their current members have the correct gender for the club on a daily basis and kick out anyone who no longer fits. So if you give someone a gender transformation drug, they will no longer be able in a club that is not meant for that gender.
- The calendar note tooltip at the bottom now only shows unfinished entries.
- Adding or creating calendar notes with a blank name now cause an exception.
- Deleting calendar notes now only considers the name and not the other properties (the previous behavior was a bug).
- Database Query operation now has an additional input to end the iteration prematurely and also frees up resources when activating the Finished output. The query operations are actually reading the entire list of results when you execute the query and then store it in memory (which can be quite a lot, depending on the query) for when you iterate through it in subsequent uses of the operation. Using the new input when you don't iterate until the end helps to keep that memory footprint low.
- VEE operations that show an image on top of them in the editor now preserve the original aspect ratio of the image instead of stretching it to the full size of the operation.
- Removed all remaining uses of "AsParallel()" in List Filter Club, Job and Stat Range operations. This could actually improve performance in cases where these operations were still used.
- Removed hardcoded randomization of arousal and energy in daily NPC stat update.

Last but not least, there are also bugfixes. Some of these may have actually had a negative effect on gameplay but were really subtle. They were only found due to the long and rigorous testing by the events team, who created dedicated test cases to reproduce these issues when they had a suspicion.

- Random Chance now works for values from 0 to 100, rather than 1 to 99. Due to the way the old code worked, even providing 100% as chance value could lead to the operation triggering the "Not Passed" output with a 1% chance.
- Fixed bug causing PersonStatsByList operation to incorrectly calculate the average value if it was used for multiple stats in the same operation.
- Fixed one constellation of PersonTraits operation removing the trait when it should actually add it.
- Duplicating SeqCond_CompareDate now properly copies link variations.
- Duplicating a ShowRandomImage operation now also duplicates the separate output links correctly, if that option is set on the source.
- Fixed some Item operations not taking over their link variations when cloning them.
- Fixed bug causing the CurrentOutfit to not properly resolve to the Club Outfit because it was comparing the name of the club location to the actual location.
- Fixed off-by-1 error potentially preventing the last item in a location from being picked up by NPCs.
- Made sure that String-to-Number conversions in VEE operations use the invariant culture, to avoid unexpected issues on specific locales.
- Fixed bug causing exception when attaching/detaching an event to a person in Try-phase while having multi-threading disabled.
- Database input field in debug menu now accepts return and tab keys.
- Database input field in VEE menu now accepts return and tab keys.
- SetPersonTextColor operation was reading variables at the wrong index.
- Fixed String Pad operation not having an output link.
- Check Rule condition now makes sure that rules have been loaded when pasting the operation.
- Fixed bug causing status effects that modify the maximum value to change the minimum value instead.
- Fixed bug causing the reloading of events in a location subfolder to also be added to the parent folder.
- Special characters now only get a random head assigned for the gender slot they are missing, rather than both.
- The Frontend now disappears and stops the task bar status after the Initialize event, rather than the StartEvent.
- Classes now only allow to add your own students to them, which fixes a bug that assigned expelled students to the "spare students" class.
- If a GameMind for the current scenario does not contain a stat adjustment for a specific stat, this will no longer throw an exception but just skip the stat.
- No longer throw an exception during daily population stat adjustments if the scenario defines no mind data.
- Fixed exception when creating second VEE directly from new process where it skips the Frontend.
- Description fields of objects in File Editor now support multi-line text.
- Fixed clubs not applying their stat changes at the end of their meeting.
- Clubs now only apply their stat changes if the person is in the location of the club meeting.
- Fixed adding/removing a person to/from a club potentially doing so one more time than intended.
- Adding a person to "no club" or no club will now remove them from their current club and no longer throw an exception.
- Pending autosaves are now also done after clicking on task buttons, interactions and using items, rather than just passing time.
- The current day is now marked as last autosave day before doing the save, so it is correctly reflected in the save file.
- Fixed typecast error in PersonStatsByList.
- Set Inventory Image operation no longer throws random exceptions during error checking.
- Bitwise Integer Math operation no longer requires a variable connected to B if ONLY the "NOT" input is used. It still does in all other cases.
- Corrected layout resizing of school subject description.
- Using up an item now clears the item details from the inventory screen.

And that's about it. I hope the current public release does not seem that much worse. After all, you probably don't even notice most of these issues unless you know what's actually supposed to happen in that very moment.

ShiloTopic starter

(Originally posted in the old forum on December 23, 2020)

Modding Framework

The modding framework was already mentioned in previous blog posts, but let's take another look at it.

My original plan was to not include the modding framework in the upcoming 1.10 release because we are already introducing so many other changes in that release. It seemed more appropriate to get a somewhat tested version out first and gather some feedback on technical issues there before opening yet another can of worms that could have negative performance impacts and concerns large parts of the engine.

However, there is also a flipside. The modding framework had been developed more or less steadily  in a separate code branch for a long time. That ultimately leads to more diverging code bases and regular maintenance effort when syncing them back up. It was effectively doubling the workload for certain parts because new code in the main release was written in an outdated way that needed to be changed again when merging it into the feature branch.
We are also toying around with the idea of migrating from VB.NET to C#, which would be least painful if we did not have to deal with multiple parallel development branches either.

So I decided to reintegrate that branch into the trunk and add the modding framework changes into the main codebase for the next release. To lessen the potential impact, there is also a legacy mode that ignores all the modding stuff and for the most part just delegates to the same underlying system calls that the engine has been using in the past (whether the legacy variant will be the default or an opt-in fallback in the next release remains to be seen).
I say "for the most part" because there are still countless places in the code that needed to be changed to point to an abstract framework, which in turn then does the actual legacy system calls or includes mods. Not all of them could be delegated exactly as they were, so it is possible that new bugs are introduced as a result of this, although we do our best to catch them in the early testing stage.

One major change that we had to make as part of the overhaul is to make sure that all scenario-specific resources use a file path that is relative to the root directory of that scenario.
Example:

In 1.9, the image paths that were referenced in events, items, etc. looked like this:
Schools\NormalSchool\Images\EventPictures\Location\Park\ParkDate.jpg
In 1.10, they will look like this:
Images\EventPictures\Location\Park\ParkDate.jpg


"Schools\NormalSchool" is the root directory of the scenario, so there is no need to explicitly declare it in events of the scenario. We are basically restricting the resources to be in a subfolder of the actual scenario that is using them. But I don't think that using images from a different scenario in your own scenario was ever an actual use case. If anything, this was more than likely a hindrance and had mods like Lexville require you to copy images from the main game into a different "ModSchool" scenario.
While there is some fallback code that will attempt to also find resources if they still start with with the "Schools\NormalSchool" part of the path, it's still recommended to update mods to the new format to be fully compatible with the new release.

Using the modding framework is pretty simple beyond that. There is now a new folder in the scenario-specific folder where you can deploy mod folders. I have been using the Church Expansion mod by protofan for my initial tests:
Schools\NormalSchool\Mods\GeneralChurchExpansion

This folder basically acts as another root directory for the scenario.
If the game tries to find a resource with a relative path like
Images\EventPictures\Location\Park\ParkDate.jpg
then it will first look in
Schools\NormalSchool\Mods\GeneralChurchExpansion\Images\EventPictures\Location\Park\ParkDate.jpg
and then continue to
Schools\NormalSchool\Images\EventPictures\Location\Park\ParkDate.jpg
if it does not find any in the mod directories. The same is true for all kinds of scenario-specific resources.

If the game tries to load all resources from a directory, it will load all resources from all directories (mods and base game) that match the requested directory name. If a resource shares the same relative path from its respective root directory as a resource from another mod or the base game, it will overwrite that resource and be used in place of it whenever possible.

The player can put mods into a specific load order to determine which mod may potentially override resources from another mod, as well as enabling and disabling specific mods. This is done via a new configuration panel in the main menu that you can see in the attached screenshot.

Mod authors can also add a file named ModInfo.xml to the root directory of their mod in order to provide some additional information for this configuration menu. Here an example that I made for the mod in the screenshot:

<?xml version="1.0" encoding="utf-8"?>
<ModInfo>
    <Name>General Church Expansion</Name>
    <Author>protofan</Author>
    <Description>This mod adds:

    6 new sfw events
    9 new nsfw events
    New images for the Church orgy
    171 new images in total</Description>
    <TitleImage>Images\EventPictures\Location\St. Judas Church\boobs.. I mean books.png</TitleImage>
</ModInfo>
Without this file, the mod will simply use its folder name as the mod name.

The selected mods also affect the resources that are available to the game's development tools. So you can activate just your mod and the mods your mod depends on in order to develop new stuff for it. The mod configuration panel also has a button to check all currently active mods for errors. Events defined in the base game are ignored by this check, so it lets you get a quick idea if a specific mod might be broken, outdated or could be incompatible as a result of your load order.

I am hoping that this will make both modding and using mods easier. Maybe Lexville can even be made to run "on top" of the regular Smallville scenario and other mods won't need to make a distinction between which of the two they support.

In any case, have a Merry Christmas and a Happy New Year.

ShiloTopic starter

(Originally posted in the old forum on Januar 08, 2022)

New system for population generation

Up to now, the population generation algorithm was pretty much hardcoded and opinionated by the programmers. While it was possible to specify the possible ranges for values in the Scenario Config files, the general family structure was unchangable and would always result in families consisting of two parents and one or more children as students.

Over the last couple of weeks, I have created a new system that aims to achieve the following goals:

  • Support different family structures
          ◦ Multi-generational families: Parents can now have parents, though they don't look any different because we don't have any elderly paperdolls yet.
          ◦ Different family branches: We can now also have aunts/uncles and nephews/nieces or cousins.
          ◦ Stepfamilies are also possible, i.e. families where a child only belongs to one of the parents, and if both parents bring their own children into the relationship, those children would be stepsiblings.
          ◦ In order to make these family structures slightly more accessible to event authors, there will be a new VEE operation that can calculate a numerical value for the degree of blood-relationship between two persons.

    I've tested this based on the family tree of the JoJo series.
    Assert.AreEqual(0, joestarJonathan.GetRelationshipDegreeTo(joestarJonathan)) // self to self
    Assert.AreEqual(1, joestarJonathan.GetRelationshipDegreeTo(joestarGeorge)) // self to father
    Assert.AreEqual(1, joestarJonathan.GetRelationshipDegreeTo(joestarMary)) // self to mother
    Assert.AreEqual(-1, joestarJonathan.GetRelationshipDegreeTo(joestarElizabeth)) // self to daughter in law
    Assert.AreEqual(3, higashikataJosuke.GetRelationshipDegreeTo(kujoJotaro)) // self to half-nephew
    Assert.AreEqual(3, kujoJotaro.GetRelationshipDegreeTo(higashikataJosuke)) // self to half-uncle
    Assert.AreEqual(5, kujoJotaro.GetRelationshipDegreeTo(giovannaGiorno)) // self to spawn of great-great-grandfather's possessed body
    Assert.AreEqual(5, giovannaGiorno.GetRelationshipDegreeTo(kujoJotaro)) // and back
    Assert.AreEqual(6, cujohJolyne.GetRelationshipDegreeTo(joestarGeorge)) // the furthest distance we have in the that family
    Assert.AreEqual(-1, joestarSuziQ.GetRelationshipDegreeTo(higashikataTomoko)) // self to husband's affair

    However, it might get a bit confused if the family tree becomes a circle (i.e. people start having children with their relatives). I can't guarantee that it will always return the closest relationship in that case.

    And I don't have any operation in place that would actually give you a usable name like "great-great-granduncle" or "third cousing thrice removed" – that would already be a headache without the potential incest.
  • Allow the generation of persons outside of the traditional parent/student families. E.g. regular childless couples, single adults or student orphans.

  • Split the monolithic template in the Scenario Config into individual files that can be replaced or added to by mods.

  • Make it easy to create variants of person templates without copying the whole file. It should be possible for example to define a "foreign" family that uses the same templates for stats, gender preferences, etc. as the default persons – and then override selected properties like the file used for naming (this is no longer just using the three hardcoded name files that we had in the past) and change it to a different file. If we later introduce additional properties like nationality, skin color or race (for example elves, orcs, dwarfs, etc. in a fantasy scenario), we could still make them based on the same generic person templates.

The system is not yet committed to the internal repo, but it's about 95% done.

Naturally, this would mean some breaking changes for existing stuff:

  • Someone added a "Create Person" operation for VEE in the past that allowed to create a person via the hardcoded population generation algorithm. This won't work out-of-the-box anymore because the new generation algorithm can't do much without specifying which template files to use for the generation. We need to change the operation so it at least takes a string parameter that tells the name of the template to use.

    Luckily this operation is not used anywhere in official events yet. And I doubt mods are using it, since it's quite new and a rare edge case.
  • Since population generation data is no longer just an ordered list of templates, the operation "Get Population Data Count" will have a bit less of a reason for existing. It was not used anywhere in official events either.

    It will still work with regards to the Population Tag count, though. The tag-to-index mapping is generally unchanged and the new system allows to explicitly assign a Population Tag to generated persons via the template, so the Population Mind still continues to work as it did before.

  • When loading special characters, the old system would still use the configuration population generation template to decide which head image index to use for the paperdoll, if no image was specified for all genders. While I made a fallback that is based on the assumption that index 1 through 100 will always be valid image files, the safer way will be to specify in the special character file which of the new person template files this character should be based off in case of missing properties.

  • The old population generation data did not need to be persisted in save files after the initial population generation. But with things like the "Create Person" operation, this may be necessary for the new system. The file size increase should be negligible, though.

How the system works:

I have separated the configuration into different subfolders of the "PopulationData" folder, each with their own responsibilities:

  • "Names" contains the text files that define possible first names or last names for population generation. It's the same format as before – one name per row – but it's now possible to use different files for different persons in the generation process. The default files have also been renamed.

  • "Parameters" contain template files that are pretty much the same as the templates that were previously defined in the Scenario Config file. However, there were some properties in the past that were defined separately for each gender but still in the same template. This is no longer the case and each template file now corresponds exactly to one person with a single gender. As of now, this gives us six files in the base game:
      - AdultMale
      - AdultFemale
      - AdultFuta
      - StudentMale
      - StudentFemale
      - StudentFuta

  • "Sources" contains files that are the transformation layer before accessing the actual template files. Instead of directly referencing a template, the population generation will instead access the "source" and the source is responsible for providing the template for the generation process.
      - The most simple case is to just delegate to the template file.
      - It's also possible to override individual properties from the retrieved file before passing it on – for example changing which naming file to use or the age range. Not all properties are currently supported for manipulation, but we can add them later if they are needed.
      - Yet another case is to have a weighted random split that will delegate to one of several possible template files. This is for example used to define a source "StudentAny", which then delegates to either one of the template files for male, female or futa students with different probabilities.

  • "Groups" define how to generate groups of persons. The actual generation process will always generate persons as groups (though you could of course have a group that only consists of a single person).

    A group consists of a list of references to files from the "Sources" folder, with each reference causing the creation of exactly one person for that group.

    The group also consists of a list of relationship definitions that reference the generation persons by their index. These relationships then basically are defined like:
      - "[person at index 0 and 1] [child of] [person at index 2 and 3]"
      - "[person at index 2] [spouse of] [person at index 1]"

    The relationship will then set them as mother/father/children/love, etc. and add some additional bonus to stats and relationship database values that were also already added by the old code. (In case of futas, the futa will be set either as mother or father, depending on which role is still left open)

The Scenario Config now only defines which of the "Groups" should be used during population generation (each with weighted randomness).

One caveat of this is that in order to change the gender ratio of the school, you now would need to change the weights in the "Sources" files for things like "StudentAny" and "AdultAny" instead of changing the weights in the Scenario Config – unless someone creates a bunch of "Groups" that only use templates for specific genders.

ShiloTopic starter

Whew, it's been two years since I actually did one of these blog entries. Well, let's pick it up again.

Operation References

With the next release, I am introducing Operation References. They are similar to Variable References, but (as the name implies) they target VEE operations instead of variables. The intention behind them is similar to lambda functions in other programming languages: you can pass a callback to an operation in the current event to a sub-event, and the sub-event can then decide to trigger that callback at the appropriate time. But it really is just something that references an operation in a specific event, so it may also have other uses in the future.


Initially I started implementing them as a whole new variable type, but then found that this would be cumbersome to handle with regards to checking for an absent value, cleaning up option values, dealing with serialization or wanting to pass/manage multiple references at once. In the end, I decided to rollback my stuff and instead just pass them around via Object variables. They already bring pretty much all the infrastructure I need (being transient, having a Nothing value to compare against, being compatible with Object Lists), and it's probably cleaner anyway to only assign those references via an explicit operation instead of a variable editor.

Then I pondered whether I should restrict them to only letting people pass references to operations in the current event or any arbitrary event. Originally I wanted to restrict them, to keep the flow of operations at least somewhat reproducible. But since I am already copying quite a bunch of UI code from the management of Variable References, I may as well also take over the field for specifying an event file name. No point in needlessly limiting the capabilities. I am also no longer limiting them to just Event Starts but instead let people reference any operation in a sequence. While not recommended, there may be some use case for it later on, where you may want to refer to an operation for other purposes than executing it.

Still, the intended use case is to trigger a callback function. And to make that pattern really obvious, there is a new entry point operation in the editor that is simply named ,,Function" and has no other purpose than to be referenced by an Operation Reference to act as start of a callback execution.



Then there is the new ,,Assign Operation Reference" operation, which lets you choose the target operation in its property window. By default, it will only show named Execution Start operations like the ,,Function" operation, but it's also possible to reference others if needed.
Variable links that expect Operation Reference variables will by convention start with an asterisk (*), reminiscent of pointer syntax in C. There's even some basic validation in VEE to warns you if you are linking variables that are used with Operation Reference links to any other links.


The last problem was how to deal with the start index for triggering executions from Operation References. The TRY phase uses start index 0, the EXEC phase uses index 1. But not all Start Events have those two indices. I didn't really want to add the Start Index to the Operation Reference itself, since that would burden the users with too much detail about how the reference will eventually be used. So I decided to just remove the option to specify a Start Index in the Event Runner (i.e. the code that is responsible for executing an event and keep track of its current context variables) altogether. Instead, the Runner should actually activate the Start Operation instead of triggering its outputs directly. Since the current Event Phase is a property of the Event Runner, I can just let the Start Event check which phase it is currently in and trigger the corresponding Try or Execute output based on that.

That solves most of the issues, except for the one case where we actually make use of the dedicated Start Index and can't just have the entry point of the runner be triggered directly: the Shopping Form and Trade Form operations. Since I originally coded those so that output links 0 and 1 are triggered as part of a new operation whenever the player moves items from one inventory to the other in the UI, they won't work without an explicit Start Index. But since those operations are used so rarely, I considered it okay to just update them to a new behavior where instead of using the output links, they get two new variable links that accept Operation References. Then we can still trigger those separate executions when the player moves stuff in the UI, but they will be triggered from the entry points specified by the references instead of the output links of the operation itself.

tl;dr: This is potentially a breaking change for mods, but I doubt that most mods make use of Trade or Shopping forms – and if they do, it's still unlikely that they also use the callback functionality to react on each transferred item in the event itself. But if they do, they should delete the old operations,  add the new versions instead and pass dedicated function callbacks instead.

ShiloTopic starter

#7
Image Compression via Animated WEBP

Those of you occasionally scouring image gallery sites to get some ideas for new content may have noticed that there are often cases where images come in several versions with only slight variations between them; a slight change in facial expressions, clothing or surrounding details while the majority of the image stays the same.

I appreciate the concept for being able to show the progression of a scene over several frames and have a bit more impact than just a static image, but it would feel kind of wasteful for HHS to spend so much disk space on only small variations of basically the same image.
We try to get the most bang for the buck and even regularly go through the process of trying to minimize the size of the included images by using certain converter/compressors. So using such sequences in full is something I usually avoid and instead tend to only pick a few key frames that have the biggest variation in progression.


At least that's what I used to think in the past. But at some point I started wondering: if those images in a sequence are really only different in a few pixels between each of them, wouldn't it be possible to store them in a more efficient way by using only the initial image and then storing only the pixels that are different to obtain the other images?

For a moment I considered whether implementing such a format myself would be worth it. But ultimately I discarded the idea because I like how HHS is somewhat simple in its asset data and everything can basically be seen, managed and modded directly in the file system – having certain image data that is only viable via a specific viewer inside the game engine itself and which may also become useless once someone decides to replace the base image would completely break with that simplicity.

Still, the idea kept living in my head rent-free. Until I eventually considered that surely other clever people thought about this before. So I threw some random word combinations at Google that might be able to describe such a feature until I eventually stumbled on something. Turns out that WEBP, the great new format that feels like the solution to so many problems, actually has exactly such a feature and uses it to reduce the file size when using it with multiple image frames in one file, i.e. animated WEBP.

So I looked into WEBP support for GIMP, took three PNG images of Claire Fuzushi of different sizes (each being roughly 92 kB in size) that only differs in her facial expression, put them together as an animation and exported them as lossless WEBP.

(The image hoster would not actually accept the original WEBP, so this is a lossy GIF to visualize the concept. You would normally not have this amount of noisy dithering and color reduction.)
Spoiler
[close]

And behold, the result of combing them was only 128 kB in size instead of 276 kB of the individual images – that's only 46%. The actual result could probably even be better, but I noticed that not all spots in the original images actually have the exact same pixel color in all places, which means it probably has to account for a little more differences than just the obvious facial expression.


At that point I decided to bother barteke with my idea, since he originally introduced WEBP support into the game. But alas the Imazen library that we were using for that did not support directly accessing a specific frame of the WEBP animation, which was a key requirement for me. After all, I only wanted to abuse the animation feature as a compression tool without actually displaying the full animation. Imazen was more working along the lines of providing a codec for WPF's built-in image element to render WEBP, but not really for accessing the format directly.

But barteke then gave SkiaSharp a try instead, which is a cross-platform 2D graphics API library that can handle lots of different formats, including WEBP. With that in place, I reworked the parts of our code that deal with image paths to make it possible to pass a numeric index in brackets after an image path of a format that supports animations (e.g. "path/to/fuzushi-tie.webp[2]"). Aside from that, I also wanted our image tagging system that is used for content filtering to support tagging individual frames differently from each other. That's why it's now set up in a way that the filter tags from the base image (without specifying an individual frame index) apply to all frames, but on top of that the system will store any tags that are only present on the individual frame on top of that.
Since SkiaSharp also supports the same for GIF, I added the same functionality there as a bonus.

And Skia is the gift that keeps on giving, because our upgrade to .NET Core 8, which I started soon afterwards, showed that Imazen was not compatible with the new .NET Core version yet – but Skia was, so we migrated all WEBP handling completely to it.

We also realized that the library that we use for showing animated GIFs in WPF really did not play well together with Skia when it comes to sharing access to image resources – if either of them would get their hands on an image file, the other would not be able to use it anymore, even after the image was supposedly no longer displayed in the application.
At that point we decided to just let Skia handle the entirety of GIFs as well. It's basically a similar approach to GIF animation that the library before that one was using: load all frames of the animation into memory and show them one after the other. The benefit is performance, but the drawback is high memory consumption for larger GIFs. The newer library was instead loading each frame into memory only for as long as it was needed, which means it never had memory issues but the CPU had lots of work to do as long as the GIF was being animated.

A small optimization that I did for the new handling was to actually turn the loaded GIF into a WPF Storyboard animation with keyframe instances for each frame of the image instead of using the naive approach with a system timer. This felt more appropriate, since I don't need to take care of stopping and disposing the timer when the image is no longer shown. The Storyboard is attached to the Image element that its animating, so if that parent element gets disposed, the Storyboard and loaded GIF frames are automatically gone as well.