Serializing classes properly

Okay, I’m in doubt about how to serialize my project’s object classes. I’ve been getting some scope problems because the objects generated by the classes were missing the toJSON () methods they needed, and after the load, things were being problematic.

My first question is, as in the documentation examples at (SugarCube v2 Documentation), what should be serialized is the objects that the class created, or the class itself? The example points to the second option.

And I followed the same example to serialize my project’s Player class, that didn’t point out any errors in the beginning.

Player.prototype._init = function(obj) {
	Object.keys(obj).forEach(function (pn) {
		this[pn] = clone(obj[pn]);
	}, this)

	return this;
}

Player.prototype.clone = function() {
	return (new Player())._init(this)
}

Player.prototype.toJSON = function () {
	const ownData = {}

	Object.keys(this).forEach(function (pn) {
		ownData[pn] = clone(this[pn]);
	}, this)

	console.log(ownData)

	return JSON.reviveWrapper('(new Player())._init($ReviveData$)', ownData)
}

It is an extensive class, getters and setters and many properties, during the load, theoretically this should be correct, but I get a Cannot set property ‘armor’ of undefined error. Aborting.

I still don’t quite understand how it works, so I’m probably missing something. I don’t think the class or object to be serialized can make a significant difference, if so, then tell me and I’ll post it.

They really don’t. All of the examples show the methods being add to the classes’ prototypes, which means they affect instances of the classes.

Copying one of the examples, essentially verbatim, isn’t likely to help much. The examples are not recipes that you can simply paste into your code—well, for simple classes they could be.

As noted in the section you linked: (emphasis added)

In both cases, since the end goal is roughly the same, this means creating a new instance of the base object type and populating it with clones of the original instance’s data. There is no one size fits all example for either of these methods because an instance’s properties, and the data contained therein, are what determine what you need to do.

In other words, you’re going to need to fit your classes’ .clone() and toJSON() methods to the classes themselves. So, yes, the makeup of the classes in question makes all the difference.

1 Like

Ah I understand why I was not getting it, in this case, the player class is currently like that. (It’s a little bit extensive) I’m going to try to adapt things.

class Player {
    constructor(firstName: string, secondName: string, _class: string) {
        this.data = {
            firstName  : firstName,
            secondName : secondName,
            race       : "human",
            id         : "playerCharacter",
            sex        : "male",
            class      : _class
        }
      
        this.spells = [];
        this.inventory = [];
        this.equipments = {
            head       : [],
            armor      : [],
            rightHand  : [],
            leftHand   : [],
            foot       : [],
            ring       : [],
            pendant    : []
        }
      
        this.status = {
            health : 100, maxHealth : 100,
            energy : 60, maxEnergy : 60,
            experience  : 0, maxExperience : 100
        }

        this.resistances = {
            physical : 0,
            fire : 0,
            ice : 0,
            energy : 0,
            water : 0,
            air : 0,
            earth : 0,
            light : 0,
            darkness : 0
        }

        this.tempEffect = [];
        this.attributes = {
            force : 3,
            endurance : 3,
            intelect : 3,
            agility : 3,
            armor : 0,
            armorPen : 0
        }

        this.myCurMovement;
        this.myCurSpell;
    }

    learnSpell = (newSpell: Spell) => {
        const _spell = clone(newSpell)
        this.spells.push(_spell)
    }

    getMove = () => {
        const _player = variables().player;

        if (_player.equipments.rightHand[0]) {
            const _equipment: Weapon = _player.equipments.rightHand[0];

            _player.myCurMovement = {
                type    : _equipment.type,
                name    : _equipment.name,
                element : _equipment.elemental.element,
                damage  : _equipment.elemental.damage(),
                setEffect() {
                    let _damage = Math.round(this.damage - setup.selectedEnemy.resistances[this.element])
                    _damage -= Math.round(setup.selectedEnemy.status.armor - _player.attributes.armorPen)
                    let _hitChance = random(0, setup.selectedEnemy.agility)
    
                    if (_player.agility > _hitChance) {
                        setup.selectedEnemy.health -= _damage;
                        switch(this.type) {
                            case 'sword':
                                return `${_damage} damage!.`
                        }
                    }
                    else {
                        return `Avoid attack.`;
                    }
                }
            }
        }
        else {
            _player.myCurMovement = {
                type    : "neutral",
                name    : "none",
                element : "physical",
                damage  : _player.force,
                setEffect() {
                    let _damage = Math.round(this.damage - setup.selectedEnemy.resistances[this.element]);
                    let _hitChance = random(0, setup.selectedEnemy.agility);
                    if (_player.agility > _hitChance) {
                        setup.selectedEnemy.health -= _damage;
                        return `${_damage} damage!`;
                    }
                    else {
                        return `Avoid attack.`;
                    }
                }
            }
        }
    }

    runTempEffect = () => {
        const _tempEffect = variables().player.tempEffect;
        _tempEffect.forEach((effect, index) => {
            if (effect.multiEffect) {
                effect.setEffect()
            }
            effect.turns -= 1;
            if (effect.turns <= 0) {
                effect.removeEffect()
                _tempEffect.splice(index, 1)
                _tempEffect[index]
                    ? _tempEffect[index].turns--
                    : ''
            }
        })
    }

    getSpell = (spell: Spell) => {
        const _player = variables().player;
        const _spell = spell;
        _spell.countdown[1] = _spell.countdown[0];
        _player.myCurSpell = {
            name    : spell.name,
            element : spell.element,
            desc    : spell.desc,
            panel   : spell.panel,
            cost    : spell.cost,
            damage  : spell.damage,

            setEffect  : spell.effect,
            runEffect() {
                const output = this.setEffect()
                setup.getBattleMenu()
                return output;
            }
        }
    }

    hasShield = () => {
        if (typeof this.equipments.leftHand === 'object') {
            return true;
        }
    }

    addItem = (_item: Weapon | Shield | Equipment) => {
        SugarCube.State.variables.player.inventory.push(_item)
        //Engine.play(SugarCube.State.passage)
        Engine.play(passage())
    }

    get invItems() {
        const _inventory = this.inventory;
        const _output: string[] = []
        if (_inventory.length > 0) {
            _inventory.forEach((item) => {
                _output.push(item.name)
            })

            return _output;
        }
        else {
            return 'You have no items.';
        }
    }

    get name() {
        return this.data.firstName;
    }

    get surname() {
        return this.data.secondName;
    }

    get sex() {
        return this.data.sex;
    }
    set sex(value) {
        this.data.sex = value;
    }

    get race() {
        return this.data.race;
    }
    set race(value) {
        this.data.race = value;
    }

    get classDesc() {
        const desc = this.data.sex + ' ' + this.data.race + ',' + ' ' + this.data.class;
        return desc;
    }
  
    get experience() {
        return this.status.experience;
    }
    set experience(value) {
        if (typeof value !== 'number') {
            throw new TypeError(`Typeof ${value} is invalid!`)
        }
        this.status.experience = value;
    }
  
    get maxExperience() {
        return this.status.maxExperience;
    }
    set maxExperience(value) {
        if (typeof value !== 'number') {
            throw new TypeError(`Typeof ${value} is invalid!`)
        }
        this.status.maxExperience = value;
    }
    get eyeColor() {
        return this.body.eyeColor;
    }
    set eyeColor(value) {
        this.body.eyeColor = value;
    }
  
    get hairColor() {
        return this.body.hairColor;
    }
    set hairColor(value) {
        this.body.hairColor = value;
    }
  
    get skinColor() {
        return this.body.skinColor;
    }
    set skinColor(value) {
        this.body.skinColor = value;
    }

    get health() {
        return this.status.health;
    }
    set health(value: number) {
        if (typeof value !== 'number') {
            throw new TypeError(`Typeof ${value} is invalid!`)
        }

        this.status.health = value;
    }
  
    get maxHealth() {
        return this.status.maxHealth;
    }
    set maxHealth(value: number) {
        this.status.maxHealth = value;
    }
  
    get energy() {
        return this.status.energy;
    }
    set energy(value) {
        if (typeof value !== 'number') {
            throw new TypeError(`Typeof ${value} is invalid!`)
        }
  
        this.status.energy = value;
    }
  
    get maxEnergy() {
        return this.status.maxEnergy
    }
    set maxEnergy(value) {
        if (typeof value !== 'number') {
            throw new TypeError(`Typeof ${value} is invalid!`)
        }
    
        this.status.maxEnergy = value;
    }
  
    get force() {
        return this.attributes.force;
    }
    set force(value: number) {
        this.attributes.force = value;
    }

    get endurance() {
        return this.attributes.endurance;
    } 
    set endurance(value: number) {
        this.attributes.endurance = value;
    }

    get agility() {
        return this.attributes.agility;
    } 
    set agility(value: number) {
        this.attributes.agility = value;
    }

    get intelect() {
        return this.attributes.intelect;
    }
    set intelect(value: number) {
        this.attributes.intelect = value;
    }

    // Item getters
    get weapon(): Weapon {
        if (this.equipments.rightHand) {
            let weapon;

            typeof this.equipments.rightHand[0] === 'object'
                ? weapon = this.equipments.rightHand[0]
                : weapon = false
            
            return weapon;
        }
    }
    set weapon(value: Weapon) {
        if (typeof this.equipments.rightHand[0] === 'object') {
            this.equipments.rightHand[0] = value;
        }
    }

    get attackType(): string {
        if (typeof this.equipments.rightHand[0] === 'object') {
            switch(this.equipments.rightHand[0].type) {
                case "sword":
                    return "Slash"
                default:
                    return "Attack"
            }
        }
        else {
            return "Attack";
        }
    }
}

Sick in bed at the moment, but I may have some recommendations later.