Author Topic: An Energy System  (Read 629 times)

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
An Energy System
« on: April 11, 2016, 03:55:47 pm »


               

So I am messing around with nwnx_jvm  (the java plugin) - and I have to say, it makes developing complex systems for nwn simpler, it also seems to handle background threads to a degree, allowing you to do Set/Get local on objects from those background threads without any apparent stack corruption.


 


One of the systems I have started building in java is a genetics system, but the next phase of the system is the 'Energy' system.



#include "energy_inc"
 
 
void main(){
 
CreateEnergy("Mental"); //1
CreateEnergy("Electrical"); //2
CreateEnergy("Thermal"); //3
CreateEnergy("Cold"); //4
CreateEnergy("Rage"); //5
CreateEnergy("Bio"); //6
CreateEnergy("Quantum"); //7
CreateEnergy("Gravitational");  //8
CreateEnergy("Umbral"); //9
CreateEnergy("Luminous"); //10
CreateEnergy("Magnetic"); //11
 
 
 
//DelayCommand(2.00,PrintEnergyTypes());
 
object oTester = GetObjectByTag("genetic_unit_test");
int EnergyID = GetEnergyTypeID("Bio");   // returns 6
 
 
AddEnergyTypeToObject(oTester, EnergyID);    //Test creature now has Bio energy
SetEnergyRechargeRate(oTester,EnergyID,5);  //Test creature now has a Bio recharge rate of 5 per 5 seconds
SetEnergyCapacity(oTester,EnergyID,300);  //Test creature now has a Bio capacity of 300 energy max 
 
 
StartEnergyHB(); //Begins the 5 second looping hb on a background thread.
 
 
}
 


 

The idea behind this is that different types of special abilities, cost different types of 'energy'.

(I am going for a low magic universe in my project, so everything has to be explained via 'some sort of energy or scientific rational')

 

For example:

If someone has the genetic trait to have super human regeneration in combat.

The regen effect would be powered from the Bio energy.

Every tick where the regeneration is applied would cost X amount of bio energy.

More Bio energy is consumed the closer to death you get, but the more health is returned etc.

When Bio energy is depleted, the effect or effects that are fueled by it will start to 'stutter' or 'function at less efficiency'.

 

I know this isn't a nwnx thread, but I thought I would share some of the inner workings of this system, in case it inspires similar trains of thought.

 

When you have your java project open : There is a class file called TestRunner.java

Might have some code that looks like this.

 




public class TestRunner {
 
public static void init() {
System.out.println("Initializing TestRunner. This class runs various sanity tests and benchmarks.");
System.out.println("If anything in this class makes your server crash, something is wrong and NEEDs to be fixed!");
System.out.println("You need to load a module that has at least one event firing on a creature sometime.");
System.out.println("Initializing the TestRunner!!");
 
 
 


 

 

Within that class is a section that looks like:



@Override
public void event(NWObject objSelf, String event) {
 
 


 

 


 

Inside this 'event' method, you can put the entry points from nwscript.

 

Eg:



if(event.startsWith("SetupEnergy_")){
NWObject.setObjectInvalidIsNull(true);
String energyName = event.replace("SetupEnergy_", "");
NWScript.setLocalInt(NWObject.MODULE,"NEW_ENERGY",0);
int iReturn = EnergyInc.CreateEnergy(energyName);
NWScript.setLocalInt(NWObject.MODULE,"NEW_ENERGY",iReturn);
}


 

This would then be triggered by the nwscript method:



int CreateEnergy(string name){
JVM_EVENT("SetupEnergy_"+name);
object oModule = GetModule();
int i = GetLocalInt(oModule,"NEW_ENERGY");
DeleteLocalInt(oModule,"NEW_ENERGY");
return i;
}


 


 

 

The more attractive aspect of the java integration, is the multi-threading capability.

 


if(event.equals("StartEnergyHB")){
(new Thread(new EnergyHeartbeat())).start();
}

 


 

This starts a Heartbeat that lives entirely inside the java domain, which repeats every 5 seconds.


public static void EnergyHeartbeat(){
 
while(true){
for(Energy e : Energy.getEnergyDefinitions()){
for(NWObject obj : getAllPlayersAndNPCForEnergySystem()){
if(e.getPlayerCanUse(obj)){
//Allow them to use the energy, so we recharge them
e.recharge(obj);
}
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}

 

While the statistics for each energy type is also stored as local vars on the module etc,

the Energy types I am creating are also created as Java classes, and stored on a static collection of EnergyTypes - allowing me to cache the information reducing the amount of times I need to invoke back to NWScript.

The only times I need to invoke back to NWScript is to do setLocal / getLocal : in order to actually recharge energy on a player/creature/object.

Much to my surprise, the jvm plugin seems to handle this very well, with no issues occurring from mainThread access etc

 


 

 



               
               

               
            

Legacy_KMdS!

  • Sr. Member
  • ****
  • Posts: 364
  • Karma: +0/-0
An Energy System
« Reply #1 on: April 11, 2016, 05:34:10 pm »


               

Remembering back to when I had to work with a java hook to nwnx about 10 years ago, I discovered that one of the things I found most interesting was the ability to run externally to NWN. in so doing, not impacting the game with the required wait for a standard NWNX retrieval of data. As long as you  don't require a immediate return result from the query within the running algorithm, it's happy days.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
An Energy System
« Reply #2 on: April 12, 2016, 02:05:11 pm »


               

Yeah, I discovered another optimization with the java portion.


Originally, in my java code - I was constructing Gene classes like this:



public class Gene {
public Gene(int geneID){
 
 
this.GeneID = geneID;
Initialize();
}
 


The initialize method would then do:

 

 



private void Initialize(){
 
String sName = NWScript.getLocalString(NWObject.MODULE, INHUMAN_POWER_STORAGE_NAME+GeneID);
int Feat = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_FEATID+GeneID);
boolean IsPassive = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_IS_PASSIVE+GeneID)==1;
int EffectType = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_EFFECT_TYPE+GeneID);
int TimeOfDay = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_TIMEOFDAY+GeneID);
int Number1 = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_NUMBER1+GeneID);
int Number2 = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_NUMBER2+GeneID);
int LevelOfPower = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_LEVEL_OF_POWER+GeneID);
int Natural = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_NATURAL+GeneID);
int Above = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_ABOVE_GROUND+GeneID);
int Interior = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_INTERIOR+GeneID);
int Surface = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_SURFACE_TYPE+GeneID);
boolean AlwaysActive = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ALWAYS_ACTIVE+GeneID)==1;
int DamageAmount = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_DAMAGE_AMOUNT+GeneID);
int DamageType = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_DAMAGE_TYPE+GeneID);
int Visual = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_VISUAL+GeneID);
boolean InCombatOnly = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_COMBAT_ONLY+GeneID)==1;
setGeneName(sName);
setFeatID(Feat);
setIsPassive(IsPassive);
setTimeOfDayActive(TimeOfDay);
setEffectType(EffectType);
setEffectNumber1(Number1);
setEffectNumber2(Number2);
setLevelOfPower(LevelOfPower);
setEnvironmentNatural(Natural);
setEnvironmentAboveGround(Above);
setEnvironmentInterior(Interior);
setEnvironmentTilesetType(Surface);
setApplyDamageAmount(DamageAmount);
setDamageType(DamageType);
setVisualEffect(Visual);
setAlwaysActive(AlwaysActive);
setCombatOnly(InCombatOnly);
 
}


 


 

This loses the speed benefit of Java, because it has 17 calls to the NWScript / Virtual Machine.

Each one of these holds up the server process by a few ms each, since the virtual machine is single threaded and operates on the MainLoop thread.

 

However, by changing my code to have a static HashMap in the jvm that caches the Gene classes, it means I remove the need for it to subsequently call NWScript.

 



private void Initialize(){
 
String sName = NWScript.getLocalString(NWObject.MODULE, INHUMAN_POWER_STORAGE_NAME+GeneID);
int Feat = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_FEATID+GeneID);
boolean IsPassive = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_IS_PASSIVE+GeneID)==1;
int EffectType = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_EFFECT_TYPE+GeneID);
int TimeOfDay = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_TIMEOFDAY+GeneID);
int Number1 = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_NUMBER1+GeneID);
int Number2 = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_NUMBER2+GeneID);
int LevelOfPower = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_LEVEL_OF_POWER+GeneID);
int Natural = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_NATURAL+GeneID);
int Above = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_ABOVE_GROUND+GeneID);
int Interior = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_INTERIOR+GeneID);
int Surface = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ENVIRONMENT_SURFACE_TYPE+GeneID);
boolean AlwaysActive = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_ALWAYS_ACTIVE+GeneID)==1;
int DamageAmount = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_DAMAGE_AMOUNT+GeneID);
int DamageType = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_DAMAGE_TYPE+GeneID);
int Visual = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_VISUAL+GeneID);
boolean InCombatOnly = NWScript.getLocalInt(NWObject.MODULE, INHUMAN_POWER_STORAGE_COMBAT_ONLY+GeneID)==1;
setGeneName(sName);
setFeatID(Feat);
setIsPassive(IsPassive);
setTimeOfDayActive(TimeOfDay);
setEffectType(EffectType);
setEffectNumber1(Number1);
setEffectNumber2(Number2);
setLevelOfPower(LevelOfPower);
setEnvironmentNatural(Natural);
setEnvironmentAboveGround(Above);
setEnvironmentInterior(Interior);
setEnvironmentTilesetType(Surface);
setApplyDamageAmount(DamageAmount);
setDamageType(DamageType);
setVisualEffect(Visual);
setAlwaysActive(AlwaysActive);
setCombatOnly(InCombatOnly);
IdToGene.put(GeneID, this);    <-- HERE
NWScript.printString("Stored gene "+this.getGeneName()+" with gene id "+GeneID);
}


 

 


 



private static HashMap<Integer, Gene> IdToGene = new HashMap<Integer, Gene>();


 

It means that when I am getting the Genome for a creature/object.

 




public Genome(NWObject player) {
 int AmountOfGenesOnCreature = NWScript.getLocalInt(player, "creature_gene_count_");
 int i;
 for (i = 1; i <= AmountOfGenesOnCreature; i++) {
  int GeneID = NWScript.getLocalInt(player, "creature_genome_storage_" + i);
  if (GeneID != 0) {
   //Gene geneToApply = new Gene(GeneID);                     <-- No longer do this
   Gene geneToApply = Gene.getGeneByID(GeneID); < --instead get the cached gene
   this.add(geneToApply);
  }
 }
}



 


 

This is an improvement on performance as it removes 17 calls to NWScript, per gene per object.

Eg: In my project, creatures and players will have genomes, which contain zero or more genes that affect special abilities etc

Arguably, I could further enhance this by removing all calls to getLocal / setLocal.

Instead using getters and setters in Java, which are called from NWScript - for any system in NWScript that needs to interact with those values.