ambiera logo

Ambiera Forum

Discussions, Help and Support.

folder icon Ambiera Forum > CopperCube > Programming and Scripting
forum topic indicator What is the best way to load behavior parameters from a file?
person icon
dekon_17
Registered User
Quote
2024-07-14 21:32:19

So far, my whole "modding" thing works out. However, big question for me right now is loading parameters. Sure, it works, but it is pretty much a big "switch" loop inside of a "for" loop, which, sure, won't cause any big lag, because items only load their parameters once - upon turning visible. What kind of bothers me is that there's a lot of lines of code that is only being executed once, and then - it's a dead weight. This isn't really a problem with the "asset loader" action, since, well, it's just an action, so the whole script is only executed once.
As of right now, this awkward thing looks like this:
[code] var WpnStats = ccbReadFileContent ("Assets/Main/" + ccbGetSceneNodeProperty (node, "Name") + "/Stats.txt").split ("\n");
for (var curr = 0; curr < WpnStats.length; curr++)
{
var pars = WpnStats [curr].split (" ");
if (curr < WpnStats.length - 1)
pars [pars.length - 1] = pars [pars.length - 1].slice (0, -1);
switch (pars [0])
{
case 'type':
this.Type = pars [2];
break;
case 'ammo':
this.Ammo = pars [1];
break;
case 'cap':
this.Capacity = parseInt (pars [1], 10);
break;
case 'rate':
this.FireRate = parseInt (pars [1], 10);
break;
case 'dist':
this.Distance = parseFloat (pars [1]);
break;
case 'angl':
this.SpreadAngle = parseFloat (pars [1]);
break;
case 'relt':
this.ReloadTime = parseInt (pars [1], 10);
break;
case 'bcr':
this.BulletCountedReload = true;
break;
case 'mass':
this.Mass = parseFloat (pars [1]);
break;
case 'dmg':
this.Damage = parseInt (pars [1], 10);
break;
case 'rec':
this.ShotRecoil = parseFloat (pars [1]);
break;
case 'pen':
this.Penetration = parseInt (pars [1], 10);
break;
case 'pun':
this.Punch = parseFloat (pars [1]);
break;
case 'mld':
this.DelMagOnEmp = true;
break;
}
}
if (this.Ammo)
{
var AmmoType = ccbReadFileContent ("Assets/Ammo/" + this.Ammo + ".txt").split ("\n");
var ProjTemp, ProjType, Speed = 0, SpeedInc = 0, SpeedMax = 0, SpeedMin = 0, LiveTime = 0, OnContact = "none", OnDecay = "none";
for (var yuy = 0; yuy < AmmoType.length; yuy++)
{
var currLine = AmmoType [yuy].split (" ");
if (yuy < AmmoType.length - 1)
currLine [currLine.length - 1] = currLine [currLine.length - 1].slice (0, -1);
switch (currLine [0])
{
case "proj":
ProjTemp = true;
ProjType = currLine [1];
break;
case "spd":
Speed = parseFloat (currLine [1], 10);
break;
case "spdinc":
SpeedInc = parseFloat (currLine [1], 10);
break;
case "spdmax":
SpeedMax = parseFloat (currLine [1], 10);
break;
case "spdmin":
SpeedMin = parseFloat (currLine [1], 10);
break;
case "time":
LiveTime = parseFloat (currLine [1], 10);
break;
case "oncontact":
OnContact = currLine [1];
break;
case "ondecay":
OnDecay = currLine [1];
break;
case "dmg":
this.Damage += parseInt (currLine [1], 10);
break;
case "rec":
this.ShotRecoil += parseFloat (currLine [1], 10);
break;
case "pen":
this.Penetration += parseInt (currLine [1], 10);
break;
case "pun":
this.Punch += parseFloat (currLine [1], 10);
break;
case "bps":
this.BulPerShot += parseInt (currLine [1], 10);
break;
}
}
if (ProjTemp)
{
this.Projectiles = true;
this.Projectile = ProjType;
this.Speed = Speed;
this.SpeedInc = SpeedInc;
this.SpeedMax = SpeedMax;
this.SpeedMin = SpeedMin;
this.LiveTime = LiveTime;
this.ImpDamag = this.Damage;
this.OnContact = OnContact;
this.OnD

person icon
okeoke
Registered User
Quote
2024-07-14 22:45:20

You might use JSON to store parameters. Deserialize all properties, and then use Object.assign or some sort of mapper function to apply them to the behaviour.
If you're targeting windows, not web, it's a bit more tricky - built in js engine does not have JSON global object implemented, but you can use eval for deserialization.

person icon
dekon_17
Registered User
Quote
2024-07-15 08:54:21

Yeah, I tried using JSON, it is not supported with this JS version.

I also just looked up the "Eval" and... Well, I don't know about it, seems a bit fishy. It is used to execute JS code, which will make mod creation process a bit harder, in my opinion. Right now it's just writing the shortname of a parameter and its value, but, with "eval", it may be more complicated. One thing I do like about that aproach is the fact that it is running JS code, meaning that it is MAYBE possible to make something more complex, in terms of modding. But, at the same time, I don't want to make it possible to use mods for some "bad" stuff. You know, JS, as well as Coppercube, includes some commands that allows people to steal, delete, and overall mess your files.

Here's an MDN statement:
"Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval()."

While I do not expect people to make just viruses, disguised as modifications for a game, I don't want to have any possibility for that in the first place. So, I think that's a no-go.

Also, forum seems to have cut my first post. Even though it was looking fine with "Preview". And it's not the first time. Will this issue be fixed?

person icon
okeoke
Registered User
Quote
2024-07-15 09:09:40

You can also use the following polyfill which doesn't use eval:https://stackoverflow.com/a/5139...

If you're using it also notice that sometimes it might parse incorrect types, most likely you will receive strings instead of numbers.

person icon
okeoke
Registered User
Quote
2024-07-15 09:15:57

Well, I guess if you want to distribute your code, it will not work, since it's just to large.

person icon
just_in_case
Moderator
Quote
2024-07-15 11:16:01

I am not sure if it will be helpful or not, but you can use .csv file, and can parse the data during runtime, just like I did with the visual novel, that way it might also be easier to mod things, and to load and save it. Instead of relying on simple plain text file.

JSON will be a good approach but if you cannot use it, you can use .csv files.

If you want you can already utilize the code from my Dialogue Manager extension available here.

https://vazahat.itch.io/dialogue...

person icon
dekon_17
Registered User
Quote
2024-07-15 15:46:02

Hm, can't say I understand anything in that code, but I will try to look into it. Though if it's just a function, maybe I should just put all that code into my own function, doubt it will make much difference.

About distributing code, the post was cut to 4000+ symbols, maximum size of posts is 8000+ symbols. Original post was about 6000 symbols, if I recall correctly. So, no idea why was it cut, but DEFINETLY not because my code is "too large".

About .csv files, I tried looking it up. As it turns out, what I was doing was, pretty much, "we-got-csv-at-home". Because here is how my weapon stats structure (pump action shotgun, in that case) looks like:
type gun shotgun
tier 4
ammo 12cal
cap 6
rate 500
dist 125
angl 15
relt 500
bcr
mass 1
cost 200
So, can't say that using .csv will make much difference. Perhaps it will make it just possible to use various programs to make or edit .csv files, but I don't know if it will make much use.

person icon
just_in_case
Moderator
Quote
2024-07-15 16:53:53

Ahh, I thought you wanted an easier way to edit those data files and all for the modding. That's why I suggested csv.

For loading parameter values for the behavior.

You can however use some function to parse the lines and remove the commas, and then do some sort of mapping for the parameter names to handler functions.
Create a file-content loader function so that you load the parameters from a file.

I think I can create a sample code file for you, let me know if you need that.

Also, if you want to load the data once in the behavior and don't want to load it again, you can simply wrap your current code into an initializer and set that initializer to true, so that you don't have to run the code again, just load the parameters once, and set it to false.

person icon
just_in_case
Moderator
Quote
2024-07-15 17:41:39

Here is a sample demo file I created for you with the code.

The code executes when you press "F" it reads the weapon stats from the file and prints them in the debug console.

https://drive.google.com/file/d/...

Also here is the code, I used the same naming schemes as your and same file structure just added temporary variable for the node name though. Commented it wherever it was necessary though I am not sure if it will be helpful to you.




//Parameters loading from a file for Dekon17, It uses the same naming scheme for variables, objects(handlers) and other data structures for ease

// parsing function for the lines from the ccbReadFileContent

function parseLine(line) {
var pars = line.split(" ");
if (pars.length > 1 && pars[pars.length - 1].slice(-1) === ",") {
pars[pars.length - 1] = pars[pars.length - 1].slice(0, -1);
}
return pars;
}

// mapping the parameter names
var WpnStats = {
type: function(pars) {
this.Type = pars[2];
print("Type:"+ this.Type); // print for debugging remove all the print command for final release
},
ammo: function(pars) {
this.Ammo = pars[1];
print("Ammo:"+ this.Ammo);
},
cap: function(pars) {
this.Capacity = parseInt(pars[1], 10);
print("Capacity:"+ this.Capacity);
},
rate: function(pars) {
this.FireRate = parseInt(pars[1], 10);
print("FireRate:"+ this.FireRate);
},
dist: function(pars) {
this.Distance = parseFloat(pars[1]);
print("Distance:"+ this.Distance);
},
angl: function(pars) {
this.SpreadAngle = parseFloat(pars[1]);
print("SpreadAngle:"+ this.SpreadAngle);
},
relt: function(pars) {
this.ReloadTime = parseInt(pars[1], 10);
print("ReloadTime:"+ this.ReloadTime);
},
bcr: function(pars) {
this.BulletCountedReload = true;
print("BulletCountedReload:"+ this.BulletCountedReload);
},
mass: function(pars) {
this.Mass = parseFloat(pars[1]);
print("Mass:"+ this.Mass);
},
dmg: function(pars) {
this.Damage = parseInt(pars[1], 10);
print("Damage:"+ this.Damage);
},
rec: function(pars) {
this.ShotRecoil = parseFloat(pars[1]);
print("ShotRecoil:"+ this.ShotRecoil);
},
pen: function(pars) {
this.Penetration = parseInt(pars[1], 10);
print("Penetration:"+ this.Penetration);
},
pun: function(pars) {
this.Punch = parseFloat(pars[1]);
print("Punch:"+ this.Punch);
},
mld: function(pars) {
this.DelMagOnEmp = true;
print("DelMagOnEmp:"+ this.DelMagOnEmp);
}
};

// load all the parameters and also read the file content here
function loadParameters(filePath, handlers) {
var fileContent = ccbReadFileContent(filePath);
var lines = fileContent.split("\n");
for (var i = 0; i < lines.length; i++) {
var pars = parseLine(lines[i]);
var handler = handlers[pars[0]];
if (handler) {
handler(pars);
}
}
}

// load weapon stats as per the node name
node = ccbGetSceneNodeFromName("cubeMesh1"); // temp node name based on my sample demo
var wpnFilePath = "Assets/Main/" + ccbGetSceneNodeProperty(node, "Name") + "/Stats.txt";

//execute the load parameter functions to read the file and handler object to it
loadParameters(wpnFilePath, WpnStats);


// we can do the same for ammo as well and other parameters that need to be loaded from the file


person icon
dekon_17
Registered User
Quote
2024-07-15 18:21:28

Guess that's the only way then. I didn't really want to make a separate function for this in the first place because, as far as I am aware, functions affect RAM usage. But, if there is no other way, that's gonna be it, maybe. Thanks for the example code, but I doubt I will use it. There is some specific stuff for different item types: guns, food, ammo, maybe something else in the future (different zombie types would be good). So, there may be some complications, I still need to come out with a solution. Anyway, thanks for help.

person icon
dekon_17
Registered User
Quote
2024-07-18 09:54:28

Okay, I guess I've done something. Not only I found out that "this", a thing I was using for a long time, was an object (even though it was pretty much on surface), I also found that I can in fact use arrays to change parameters of them. In the end, I made two functions - one for getting the parameter and applying them to object, and one for combining parameters (required for firearms):
function ItemStats_Parse (obj, file)
{
var Stats = ccbReadFileContent (file).split ("\n");
for (var curr = 0; curr < Stats.length; curr++)
{
var StatLine = Stats [curr].split (" ");
if (curr < Stats.length - 1)
StatLine [StatLine.length - 1] = StatLine [StatLine.length - 1].slice (0, -1);
switch (StatLine [0])
{
case 'type':
obj.type = StatLine [1];
obj.subtype = StatLine [2];
break;
case 'ammo':
obj.ammo = StatLine [1];
break;
case 'cap':
obj.capacity = parseInt (StatLine [1], 10);
break;
case 'rate':
obj.attackrate = parseInt (StatLine [1], 10);
break;
case 'dist':
obj.distance = parseFloat (StatLine [1]);
break;
case 'angl':
obj.angle = parseFloat (StatLine [1]);
break;
case 'relt':
obj.reloadtime = parseInt (StatLine [1], 10);
break;
case 'bcr':
obj.bulletcountedreload = true;
break;
case 'mass':
obj.mass = parseFloat (StatLine [1]);
break;
case 'dmg':
obj.damage = parseInt (StatLine [1], 10);
break;
case 'rec':
obj.recoil = parseFloat (StatLine [1]);
break;
case 'pen':
obj.penetration = parseInt (StatLine [1], 10);
break;
case 'pun':
obj.punch = parseFloat (StatLine [1]);
break;
case 'mld':
obj.deletemag = true;
break;
case 'bps':
obj.pershot = parseInt (StatLine [1], 10);
break;
case 'proj':
obj.projname = StatLine [1];
break;
case 'spd':
obj.projspd = parseFloat (StatLine [1]);
break;
case 'spdinc':
obj.projspdinc = parseFloat (StatLine [1]);
break;
case 'spdmax':
obj.projspdmax = parseFloat (StatLine [1]);
break;
case 'spdmin':
obj.projspdmin = parseFloat (StatLine [1]);
break;
case 'time':
obj.livetime = parseInt (StatLine [1], 10);
break;
case 'onimpact':
obj.onimpact = StatLine [1];
break;
case 'ondecay':
obj.ondecay = StatLine [1];
break;
}
}
}

function ItemStats_Combine (main, toAdd)
{
var stats = ['capacity', 'attackrate', 'distance', 'angle', 'reloadtime', 'bulletcountedreload', 'mass', 'damage', 'recoil', 'penetration', 'punch', 'deletemag', 'pershot', 'projname', 'projspd', 'projspdinc', 'projspdmax', 'projspdmin', 'livetime', 'onimpact', 'ondecay'];
for (var xd = 0; xd < stats.length; xd++)
{
if (toAdd [stats [xd]])
{
if (main [stats [xd]] != undefined)
main [stats [xd]] += toAdd [stats [xd]];
else
main [stats [xd]] = toAdd [stats [xd]];
}
}
}
In the end, only a small code is required to actually load parameters:
	var WpnStats = "Assets/Main/" + ccbGetSceneNodeProperty (node, "Name") + "/Stats.txt";
ItemStats_Parse (this, WpnStats);
if (this.ammo)
{
var AmmoObj = {};
var AmmoType = "Assets/Ammo/" + this.ammo + ".txt";
ItemStats_Parse (AmmoObj, AmmoType);
ItemStats_Combine (this, AmmoObj);
if (AmmoObj.projname)
this.Projectiles = true;
}
if (this.pershot < 1)
this.pershot = 1;
Though I still think there is a room for optimization, the current result seems good to me. Though I'll need to consider making more of those parameters universal (like, speed can be applied to projectiles and enemies, something like that), so I won't have to make even more of them, making function even bigger.

I wonder hom much I still don't know about Coppercube programming, after all that.


Create reply:










 

  

Possible Codes


Feature Code
Link [url] www.example.com [/url]
Bold [b]bold text[/b]
Image [img]http://www.example.com/image.jpg[/img]
Quote [quote]quoted text[/quote]
Code [code]source code[/code]

Emoticons


icon_holyicon_cryicon_devilicon_lookicon_grinicon_kissicon_monkeyicon_hmpf
icon_sadicon_happyicon_smileicon_uhicon_blink   






Copyright© Ambiera e.U. all rights reserved.
Contact | Imprint | Products | Privacy Policy | Terms and Conditions |