I haven’t had time to read through this thread, but I wanted to chime in with some high level advice. I’ve made a Vassal module for my own game design that has the same level of rules enforcement as a product one might purchase on Steam. I learned 2 EXTREMELY important things about Calculated Properties versus Dynamic/Global Properties and Global Key Commands.
- Do not fear massive arithmetic expressions for Calculated Properties. Modern computers are incredibly fast. Thousands of times faster than the when I first started programming in the 80s. Unless Vassal or Java requires massive overhead (lots of code performing junk I have no control over), calculations that are daunting if done by hand, are completed in a nanosecond. I routinely add expressions like this one to every piece, and it doesn’t affect performance one iota. This counts how many pending battles are in all 55 sea zones:
Integer.toString(GetProperty(“Adriatic Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Aegean Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Alboran Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Balearic Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Baltic Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Barcelona_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Barents Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Bay of Biscay_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Bothnian Bay_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Bothnian Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Canadian Coastal Zone_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Celtic Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Central Atlantic Ocean_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Central Mediterranean_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Cilician Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Denmark Strait_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Eastern Black Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Eastern Mediterranean_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Eastern Sea Frontier_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“English Channel_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Fladen Ground_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“German Bight_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Greenland Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Gulf of Cadiz_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Gulf of Finland_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Gulf of Sirte_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Gulf Sea Frontier_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Iberian Coast_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Indian Ocean_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Ionian Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Irish Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Irminger Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Levantine Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Libyan Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Ligurian Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Nordkapp Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“North Atlantic Ocean_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“North Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Northern Caspian_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Norwegian Leads_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Norwegian Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Red Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Sea of Azov_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Skaggerak-Kattegat_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“South Atlantic Ocean_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Southern Bight_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Southern Caspian_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Tyrrhenian Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Western Approaches_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Western Baltic_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Western Black Sea_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“Western Mediterranean_Units”)).replace(“0”,"").length()/2+Integer.toString(GetProperty(“White Sea_Units”)).replace(“0”,"").length()/2
NOTE: The reason why it’s “instantaneous” is that every property is a Global Property that was determined BEFORE this calculation was performed. If those properties had to be determined on the fly, that expression would be a performance trainwreck…
- GKCs or any of the new SUM / COUNT operations DO have massive Vassal and Java overhead. Even an operation that affects only one piece can create a perceptible delay. I’d estimate that these functions are several orders of magnitude more CPU intensitve than my ridiculous Calculated Property above. The trick with these operations is to make them “on demand”, meaning they should never be associated with Calculated Properties or anything else that occurs in the background without direct player interaction. I use Dynamic Properties and Tiggers to invoke these types of operations at moments a player doesn’t even notice. For instance, I avoid them when a player is moving a piece. Players expect instantaneous response when they are dragging and dropping a piece. Any lag is immediately noticed. But if a player flips a counter or uses a Send to Location, there is a quarter second delay as their brain is looking at the new image and taking it all in. If you perform a GKC or SUM / COUNT at that instant, players will never notice that delay. Programmer sleight of hand…
My main point is that it doesn’t matter if you use “brute force” to implement your economic system, as long as players don’t perceive a time delay, your code can be ugly as all heck. Players will never know…