Author Topic: Scripting checkpoints  (Read 140 times)

Galavant Garde

  • Full Member
  • ***
  • Posts: 150
  • Cayelan Mendoza
    • View Profile
    • Galavant Garde - Dark Electro Disco Pop
    • Email
Scripting checkpoints
« on: January 05, 2018, 08:30:37 AM »
Oh hi me again, so I've implemented Dim3 checkpoints for the first time (I always just used to use my own manually scripted checkpoints for simple save stuff) so that I can get my game to auto-save.  It seemed to work OK at the beginning, but now I'm getting problems with it.  I noticed when you load a game from a checkpoint, the checkpoint is still there after loading, triggering another save event in the same spot.  So you end up with near-identical saves in the same spot for each time you load it.  Also this saving-right-after-loading-because-the-checkpoint-is-still-there thing often causes the engine to crash.  Is there a way to add a script to the checkpoint that prevents it from re-triggering after loading the save file?  Or something in the engine/editor that removes triggered checkpoints from save files?

Alternatively, is there a command I can run that triggers the auto-save event?  That way I could get the game to save without bringing up the Save Game interface WHILE simultaneously avoiding the above checkpoint issue altogether?

Also the map.action.restartMapFromSave(); API isn't working for me - it just restarts the map even though there is definitely a save file.  Other times it comes up saying something about it can't find the spot '-Player'

Hopefully it's just something I'm doing wrong.  Sorry for all the questions - I know I'm the only one really posting anything these days but I'm really fond of Dim3 and want to keep using it

ggadwa

  • Administrator
  • Full Member
  • *****
  • Posts: 207
    • View Profile
    • Klink! Software
Re: Scripting checkpoints
« Reply #1 on: January 05, 2018, 09:04:23 AM »
That simple save was implemented for Scruffy and I doubt it was ever tested in any other place and it is probably buggy and not you.  I suspect (I'll have to relook over the code) that it's really works in a very "restart the entire level" type of thing, and not a save the current state.  It's probably not something very useful for more complex games.

[>] Brian

Galavant Garde

  • Full Member
  • ***
  • Posts: 150
  • Cayelan Mendoza
    • View Profile
    • Galavant Garde - Dark Electro Disco Pop
    • Email
Re: Scripting checkpoints
« Reply #2 on: January 05, 2018, 09:34:11 AM »
Simple saves work great; it's the entire game state saves that I'm getting stuck with.  So is the map.action.restartMapFromSave(); a command intended for simple saves then?  I assumed it must be for the full game state saves.  If it is for simple saves, would it for example reload the map while simultaneously returning the simple save ID or something like that?

Galavant Garde

  • Full Member
  • ***
  • Posts: 150
  • Cayelan Mendoza
    • View Profile
    • Galavant Garde - Dark Electro Disco Pop
    • Email
Re: Scripting checkpoints
« Reply #3 on: January 05, 2018, 11:45:51 AM »
Meanwhile I've written a routine into my game script that constructs a 9-digit simple save ID with each number representing a different parameter.  I used a much more basic version of this with 2 digits when I made Ronja all those years ago.  Anyway I can use this to manually reconfigure the level after loading it so it works a little more like a full game save state.  At the moment it allows me to know which map, which music, which checkpoint to move the player to, what the player's health was, whether the player is carrying a rock, the player's model animation settings (he runs differently depending on what's going on), and whether bots are alerted to the player's presence or not.

It's a bit much to try incorporate stuff like enemy locations / health / if they're dead / what they were in the middle of, but I'm sure I'll come up with ways to squeeze more data in there  :D

EDIT - I rounded this bit out.  So yes I've made a simple save ID that clumps together basic data so loading a map after quitting the engine can put the player back where he was and with the same vitals / equipment, but all that remains of the bots' previous state is their collective awareness of the player...they sadly get sent back to their starting spots (because simple save IDs can't pack enough data into them to include the x/y/z/angY details of all the bots.  What I HAVE therefore been able to do is, while you're still playing i.e. you die and respawn but you don't quit and reopen the engine, the game remembers where to put all the bots and restores them fairly faithfully to their states as of the time you hit the checkpoint.

It involves using For loops in the game script that call bots sequentially to gather important vitals/stats, and stores them in arrays inside the game script. I'm then able to run more For loops to manually return all those values to every bot when the player calls the game telling it to restore the bots' old state.  Effectively, it's an auto-save that returns things essentially back to the way they were after the player hit the checkpoint but before the player died.

And it's fast!  No visible lag whatsoever - it just seamlessly puts things back the way they were.  Here's a messy look at how it works:

Code: [Select]
// SAVE ROUTINE - in player script

// Simple save ID will effectively form an array-like string - use JS string methods to read it. The positions will mean the following:

// 0 - Map              // 0 is Yard, 1 is Road, 2 is Sewers
// 1 - Music            // 0 is Prison, 1 is Prison_beat
// 2 - Checkpoint ID    // used to allow searching for the checkpoint, which allows pinpointing its map position so the player may be placed there
// 3-5 - Health         // occupies 3 places as the player default health is 200
// 6 - Holding rock or not  // 0 - false, 1 - true
// 7 - Player move animation    // 0 - Trudge, 1 - Limp, 2 - Run, 3 - Sprint
// 8 - Global bot awareness // 0 - Low, 1 - Medium, 2 - High

// When respawning without quitting, individual bot data can be restored. If loading a simple save, only the above parameters will be applied meaning
// only general data can be used to set bots up. This means their location and movement data won't restore, and they will all have the same awareness

function constructSaveString(game,x,y,z) // note the checkpoint includes its x/y/z position when calling the game script
{
    pos0=data.get('map name');  // this is set when the map loads
    pos0=pos0.toString();   // convert to a string - this will mean all other values added at end will be strings also
    pos1=data.get('current music');  // this is set when the music starts
    pos2=data.get('checkpoint');  // this is set when the checkpoint object spawns into the map

    playerhealth=map.object.getHealth(0);
    data.add('health',playerhealth);
    playerhealth=playerhealth.toString();   // convert to string otherwise the JS slice method cannot be used

    if (playerhealth>99) {  // 3 digits
        pos3=playerhealth.slice(0,1);
        pos4=playerhealth.slice(1,2);
        pos5=playerhealth.slice(2);
        }
    else if (playerhealth>9) {  // 2 digits
        pos3=0;
        pos4=playerhealth.slice(0,1);
        pos5=playerhealth.slice(1);
        }
    else {  // 1 digit
        pos3=0;
        pos4=0;
        pos5=playerhealth;
    }

    if (data.get('holding rock')==null) pos6=0; // data container won't exist if a rock was never picked up
    else pos6=data.get('holding rock');

    pos7=data.get('move animation');  // set whenever player walk animation changes
    pos8=data.get('alert level');  // set globally by the last bot to change its alert level, default is 0

    simpleSaveString=pos0+pos1+pos2+pos3+pos4+pos5+pos6+pos7+pos8;
    map.action.setSimpleSave(simpleSaveString,map.info.title);

    data.add('respawn x',x);
    data.add('respawn y',y);
    data.add('respawn z',z);
    respawnAngle=map.object.getAngle(0);
    data.add('respawn angle',respawnAngle.y.toFixed(0));    // Y angle with no decimals rounded up

    for (i=0; i<botRoll.length; i++) {  // runs bot data capture for each ID stored on the roll array
        botX.push(game.event.callObjectById(botRoll[i],'captureX',null));
        botY.push(game.event.callObjectById(botRoll[i],'captureY',null));
        botZ.push(game.event.callObjectById(botRoll[i],'captureZ',null));
        botYangle.push(game.event.callObjectById(botRoll[i],'captureYang',null));
        botAlert.push(game.event.callObjectById(botRoll[i],'captureAwareness',null));
        botPath.push(game.event.callObjectById(botRoll[i],'capturePath',null));
        botMove.push(game.event.callObjectById(botRoll[i],'captureMovement',null));
        botHealth.push(game.event.callObjectById(botRoll[i],'captureHealth',null));
    }

    iface.console.write('auto-save complete - '+simpleSaveString);
}

All those calls to the bots look like this:

Code: [Select]
// This is in each bot script
function captureX(obj)
{
return(obj.position.x);
}

function captureY(obj)
{
return(obj.position.y);
}

function captureZ(obj)
{
return(obj.position.z);
}

function captureYang(obj)
{
return(obj.angle.y.toFixed(0)); // Y angle rounded up with no decimals
}

function captureAwareness(obj)
{
return(awareness);
}

function capturePath(obj)
{
return(pathID);
}

function captureMovement(obj)
{
return(moving);
}

function captureHealth(obj)
{
return(obj.health.current);
}

From that, when the player dies it calls:

Code: [Select]
// this is in the player script - attached to the Die event
function die(obj)
{
    hideHUD(obj);
    freezePlayer(obj);

    sound.stopMusic();
sound.playGlobalPlayer("Game Over",1);

obj.motionVector.stop();
obj.motionAngle.turnStop();

dying=true;

obj.model.animation.start('Die');
obj.event.chain(1,'playerFallOver');
}

function playerFallOver(obj)
{
obj.event.callGame('playerHasDied',null);
    headTurn=new Angle(90,90,0);
    camera.angle.turn(headTurn,2000);
    obj.status.tintView(black,1,2000,3500,500);
    obj.event.chain(25,'checkForSaveData');
}

function checkForSaveData(obj)
{
obj.setting.hidden=true;

if (data.get('health')==null) obj.event.chain(10,'resetCamera');
else obj.event.chain(10,'resumeFromCheckpoint');
}

function resumeFromCheckpoint(obj)
{
obj.event.callGame('restoreBotState',null);
camera.angle.x=0;
camera.angle.y=0;
camera.angle.z=0;
obj.event.chain(20,'raiseTheCurtain');
}

function raiseTheCurtain(obj)
{
posX=data.get('respawn x');
posY=data.get('respawn y');
posZ=data.get('respawn z');
spot=new Point(posX,posY,posZ);
ang=data.get('respawn angle');
obj.position.place(spot,ang);
obj.health.add(data.get('health'));
obj.model.animation.start('Breathing');

obj.setting.hidden=false;

if (obj.health.current<50) walkSlower(obj);
    else if (obj.health.current<100) walkNormal(obj);
    else if (obj.health.current<150) runSlower(obj);
    else runNormal(obj);

iface.bitmap.hideAll();
dying=false;
unfreezePlayer(obj);

musicID=data.get('current music');
switch (musicID) {
case 0:
sound.startMusic('Prison');
return;

case 1:
sound.startMusic('Prison_beat');
return;
}

if (checkpoint1) checkpoint1=false;
}

You'll see in that second bit, the player calls the game after dying:

Code: [Select]
// this is in the game script
function playerHasDied(game)
{
    for (i=0; i<botRoll.length; i++) game.event.callObjectById(botRoll[i],'stopAllActivity',null);
}

That cycles through every bot telling them to stop what they're doing.  After they're all done, the player calls the game asking it to do this:

Code: [Select]
// this is also in the game script
function restoreBotState(game)
{
    for (i=0; i<botRoll.length; i++) {
        if (botHealth[i]<1) iface.console.write('skipping restore of bot '+botRoll[i]+' because he is dead');
        else game.event.callObjectById(botRoll[i],'restoreState',botX[i],botY[i],botZ[i],botYangle[i],botAlert[i],botPath[i],botMove[i]);
        }
    iface.console.write('all bots restored');
}

Which checks the health the bot was saved with.  If it has no health, it won't bother restoring its old state because it's dead so hasn't changed state.  If it WASN'T dead at the time of hitting the checkpoint, it reconstructs all of the saved data and packages it up into a call to each bot script sequentially:

Code: [Select]
// this is in each bot script
function restoreState(obj,x,y,z,angY,prevAwareness,prevPathID,prevMoving)
{
obj.health.reset();
    obj.setting.contact=true;

spot=new Point(x,y,z);
obj.position.place(spot,angY);

switch (prevAwareness) {
case 0:
lowAlert(obj);
break;

case 1:
mediumAlert(obj);
break;

case 2:
highAlert(obj);
break;
}

obj.watch.start(listeningDistance);
pathID=prevPathID; // revert to previous path iD
if (flashlight) useFlashlight(obj,false);
if (prevMoving) resumeInterruptedPath(obj); // if moving, resume previous path
else if (pathID==null) spawnOntoMap(obj);
else whereToNext(obj); // if not moving, decide on a new path based on previous awareness
}

Moral of the story?  There's always a way, even for coding noobs like me