Skip to main content

On Sale: GamesAssetsToolsTabletopComics
Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

TheAlCat

27
Posts
2
Topics
11
Followers
46
Following
A member registered Sep 30, 2023 · View creator page →

Creator of

Recent community posts

Patreon access for Sara's Turn Based Battle System - itch.io

This link should work.

Game data code:

//Action Library

global.actionLibrary =

{

   attack :

   {

   name : "Attack",

   description : "{0} attacks!",

   subMenu : -1,

   targetRequired : true,

   targetEnemyByDefault : true,

   targetAll : MODE.NEVER,

   userAnimation : "attack",

   effectSprite : sAttackBonk,

   effectOnTarget : MODE.ALWAYS,

   func : function(_user, _targets)

   {

   var _damage = ceil(_user.strength + random_range(-_user.strength * 0.25, _user.strength * 0.25));

   BattleChangeHP(_targets[0],-_damage,0);

   }

   },

   ice :

   {

   name : "Ice",

   description : "{0} casts Ice!",

   subMenu : "Magic",

   mpCost : 4,

   targetRequired : true,

   targetEnemyByDefault : true, //0: party/self, 1: enemy

   targetAll : MODE.VARIES,

   userAnimation : "cast",

   effectSprite : sAttackIce,

   effectOnTarget : MODE.ALWAYS,

   func : function(_user, _targets)

   {

   var _damage = irandom_range(10,15);

   BattleChangeHP(_targets[0],-_damage,);

   //BattleChangeMP(_user, -mpCost);

   }

   }

}

enum MODE

{

NEVER = 0,

ALWAYS = 1,

VARIES = 2

}

//Party data

global.party = 

[

{

name: "Lulu",

hp: 89,

hpMax: 89,

mp: 15,

mpMax: 15,

strength: 6,

sprites : { idle: sLuluIdle, attack: sLuluAttack, defend: sLuluDefend, down: sLuluDown},

actions : [global.actionLibrary.attack]

}

,

{

name: "Questy",

hp: 44,

hpMax: 44,

mp: 30,

mpMax: 30,

strength: 4,

sprites : { idle: sQuestyIdle, attack: sQuestyCast, cast: sQuestyCast, down: sQuestyDown},

actions : [global.actionLibrary.attack, global.actionLibrary.ice]

}

]

//Enemy Data

global.enemies =

{

slimeG: 

{

name: "Slime",

hp: 30,

hpMax: 30,

mp: 0,

mpMax: 0,

strength: 5,

sprites: { idle: sSlime, attack: sSlimeAttack},

actions: [global.actionLibrary.attack],

xpValue : 15,

AIscript : function()

{

//attack random party member

var _action = actions[0];

var _possibleTargets = array_filter(oBattle.partyUnits, function(_unit, _index)

{

return (_unit.hp > 0);

});

var _target = _possibleTargets[irandom(array_length(_possibleTargets)-1)];

return [_action, _target];

}

}

,

bat: 

{

name: "Bat",

hp: 15,

hpMax: 15,

mp: 0,

mpMax: 0,

strength: 4,

sprites: { idle: sBat, attack: sBatAttack},

actions: [global.actionLibrary.attack],

xpValue : 18,

AIscript : function()

{

//attack random party member

var _action = actions[0];

var _possibleTargets = array_filter(oBattle.partyUnits, function(_unit, _index)

{

return (_unit.hp > 0);

});

var _target = _possibleTargets[irandom(array_length(_possibleTargets)-1)];

return [_action, _target];

}

}

}

Battle Functions code:

function NewEncounter(_enemies, _bg)

{

instance_create_depth

(

camera_get_view_x(view_camera[0]),

camera_get_view_y(view_camera[0]),

-9999,

oBattle,

{enemies: _enemies, creator: id, battleBackground: _bg}

);

}

function BattleChangeHP(_target, _amount, _AliveDeadOrEither = 0)

{

//_AliveDeadOrEither: 0 = alive only, 1 = dead only, 2 = any

var _failed = false;

if (_AliveDeadOrEither == 0) and (_target.hp <= 0) _failed = true;

if (_AliveDeadOrEither == 1) and (_target.hp > 0) _failed = true;

var _col = c_white;

if (_amount > 0) _col = c_lime;

if (_failed)

{

_col = c_white;

_amount = "failed";

}

instance_create_depth

(

_target.x,

_target.y,

_target.depth-1,

oBattleFloatingText,

{font : fnM5x7, col: _col, text : string(_amount)}

);

if (!_failed) _target.hp = clamp(_target.hp + _amount, 0, _target.hpMax);

}

oBattle step event code:

battleState();

//Cursor control

if (cursor.active)

{

with (cursor)

{

//input

var _keyUp = keyboard_check_pressed(vk_up);

var _keyDown = keyboard_check_pressed(vk_down);

var _keyLeft = keyboard_check_pressed(vk_left);

var _keyRight = keyboard_check_pressed(vk_right);

var _keyToggle = false;

var _keyConfirm = false;

var _keyCancel = false;

confirmDelay++

if (confirmDelay > 1)

{

_keyConfirm = keyboard_check_pressed(vk_enter);

_keyCancel = keyboard_check_pressed(vk_escape);

_keyToggle = keyboard_check_pressed(vk_shift);

}

var _moveH = _keyRight - _keyLeft;

var _moveV = _keyDown - _keyUp;

if (_moveH == -1) targetSide = oBattle.partyUnits;

if (_moveH == 1) targetSide = oBattle.enemyUnits;

//verify target list

if (targetSide == oBattle.enemyUnits)

{

targetSide = array_filter(targetSide, function(_element, _index)

{

return _element.hp > 0;

});

}

//move between targets

if (targetAll == false) //Single target mode

{

if (_moveV == 1) targetIndex++;

if (_moveV == -1) targetIndex--;

//wrap

var _targets = array_length(targetSide);

if (targetIndex < 0) targetIndex = _targets - 1;

if (targetIndex > (_targets - 1)) targetIndex = 0;

//identify target

activeTarget = targetSide[targetIndex];

//toggle all mode

if (activeAction.targetAll == MODE.VARIES) and (_keyToggle) //switch to all mode

{

targetAll = true;

}

}

else //target all mode

{

activeTarget = targetSide;

if (activeAction.targetAll == MODE.VARIES) and (_keyToggle) //switch to single mode

{

targetAll = false;

}

}

//Confirm action

if (_keyConfirm)

{

with (oBattle) BeginAction(cursor.activeUser, cursor.activeAction, cursor.activeTarget);

with (oMenu) instance_destroy();

active = false;

confirmDelay = 0;

}

//Cancel and return to menu

if (_keyCancel) and (!_keyConfirm)

{

with (oMenu) active = true;

active = false;

confirmDelay = 0;

}

}

}

oBattle create event code:

instance_deactivate_all(true);

units = [];

turn = 0;

unitTurnOrder = [];

unitRenderOrder = [];

turnCount = 0;

roundCount = 0;

battleWaitTimeFrames = 30; 

battleWaitTimeRemaining = 0;

battleText = "";

currentUser = noone;

currentAction = -1;

currentTargets = noone;

//Make targetting cursor

cursor =

{

activeUser : noone,

activeTarget : noone,

activeAction : -1,

targetSide : -1,

targetIndex : 0,

targetAll : false,

confirmDelay : 0,

active : false

};

//Make enemies

for (var i = 0; i < array_length(enemies); i++)

{

enemyUnits[i] = instance_create_depth(x+250+(i*10),y+68+(i*20),depth-10,oBattleUnitEnemy, enemies[i])

array_push(units, enemyUnits[i]);

}

//Make party

for (var i = 0; i < array_length(global.party); i++)

{

partyUnits[i] = instance_create_depth(x+70+(i*10),y+68+(i*15),depth-10,oBattleUnitPC, global.party[i])

array_push(units, partyUnits[i]);

}

//Shuffle turn order

unitTurnOrder = array_shuffle(units);

//Get render order

RefreshRenderOrder = function()

{

unitRenderOrder = [];

array_copy(unitRenderOrder,0,units,0,array_length(units));

array_sort(unitRenderOrder,function(_1, _2)

{

return _1.y - _2.y;

});

}

RefreshRenderOrder();

function BattleStateSelectAction()

{   

if (!instance_exists(oMenu))

{

//Get current unit

var _unit = unitTurnOrder[turn];

//is the unit dead or unable to act?

if (!instance_exists(_unit)) or (_unit.hp <= 0)

{

battleState = BattleStateVictoryCheck;

exit;

}

//Select an action to perform

//BeginAction(_unit.id, global.actionLibrary.attack, _unit.id);

//if unit is player controlled:

if (_unit.object_index == oBattleUnitPC)

{

//Compile the action menu

var _menuOptions = [];

var _subMenus = {};

var _actionList = _unit.actions;

for (var i = 0; i < array_length(_actionList); i++)

{

var _action = _actionList[i];

var _available = true; //later we will replace this to check mp.

var _nameAndCount = _action.name; //later we will replace this

if (_action.subMenu == -1)

{

array_push(_menuOptions, [_nameAndCount, MenuSelectAction, [_unit, _action],_available]);

}

else

{

//create or add to a submenu

if (is_undefined(_subMenus[$ _action.subMenu]))

{

variable_struct_set(_subMenus, _action.subMenu,[[_nameAndCount,MenuSelectAction,[_unit,_action],_available]]);

}

else

{

array_push(_subMenus[$ _action.subMenu], [_nameAndCount, MenuSelectAction, [_unit, _action], _available]);

}

}

}

//turn sub menus into an array

var _subMenusArray = variable_struct_get_names(_subMenus);

for (var i = 0; i < array_length(_subMenusArray); i++)

{

//sort submenus if needed

//(here)

//add back option at the end of each submenu

//array_push(_subMenus[$ _subMenusArray[i]], ["Back", MenuGoBack -1, true]);

if (_action.subMenu == -1) array_push(_subMenus[$ _subMenusArray[i]], ["Back", MenuGoBack -1, true]);

//add submenu into main menu

//array_push(_menuOptions,[_subMenusArray[i], SubMenu, [_subMenus[$ _subMenusArray[i]]],true]);

if (_action.subMenu == -1) array_push(_menuOptions,[_nameAndCount, MenuSelectAction, [_unit, _action], _available]);

}

Menu(x+10, y+110, _menuOptions, , 74, 60)

}

else

{

//if unit is AI controlled:

var _enemyAction = _unit.AIscript();

if (_enemyAction != -1) BeginAction(_unit.id, _enemyAction[0], _enemyAction[1]);

}

  }

}

function BeginAction(_user, _action, _targets)

{

currentUser = _user;

currentAction = _action;

currentTargets = _targets;

battleText = string_ext(_action.description,[_user.name]);

if (!is_array(currentTargets)) currentTargets = [currentTargets];

battleWaitTimeRemaining = battleWaitTimeFrames;

with (_user)

{

acting = true;

//play user animation if it is defined for that action and that user

if (!is_undefined(_action[$ "userAnimation"])) and (!is_undefined(_user.sprites[$ _action.userAnimation]))

{

sprite_index = sprites[$ _action.userAnimation];

image_index = 0;

}

}

battleState = BattleStatePerformAction;

}

function BattleStatePerformAction()

{

//if animation etc is still playing

if (currentUser.acting)

{

//when it ends, perform action effect if it exists

if (currentUser.image_index >= currentUser.image_number -1)

{

with (currentUser)

{

sprite_index = sprites.idle;

image_index = 0;

acting = false;

}

if (variable_struct_exists(currentAction, "effectSprite"))

{

if (currentAction.effectOnTarget == MODE.ALWAYS) or ( (currentAction.effectOnTarget == MODE.VARIES) and (array_length(currentTargets) <= 1) )

for (var i = 0; i < array_length(currentTargets); i++)

{

instance_create_depth(currentTargets[i].x,currentTargets[i].y,currentTargets[i].depth-1,oBattleEffect,{sprite_index : currentAction.effectSprite})

}

}

else //play at 0,0

{

var _effectSprite = currentAction.effectSprite

if (variable_struct_exists(currentAction,"effectSpriteNoTarget")) _effectSprite = currentAction.effectSpriteNoTarget;

instance_create_depth(x,y,depth-100,oBattleEffect,{sprite_index : _effectSprite});

}

}

currentAction.func(currentUser, currentTargets);

}

else //wait for delay and then end the turn

   if (!instance_exists(oBattleEffect))

   {

   battleWaitTimeRemaining--

   if (battleWaitTimeRemaining == 0)

   {

   battleState = BattleStateVictoryCheck;

   }

   }

}

}

function BattleStateVictoryCheck()

{

battleState = BattleStateTurnProgression;

}

function BattleStateTurnProgression()

{

battleText = ""; //reset battle text

turnCount++; //total turns

turn++;

//Loop turns

if (turn > array_length(unitTurnOrder) - 1)

{

turn = 0;

roundCount++;

}

battleState = BattleStateSelectAction;

}

battleState = BattleStateSelectAction;

Instead of taking turns with the damage, for whatever reason, when a player  targets an enemy or the enemy targets a player, the damage is done all at once making the hp go down to zero and having multiple "failed" messages.

Version: IDE version 2024.13.1.193  Runtime version 2024.13.1.242

I'm not sure what is causing the problem but I haven't been able to eleminate the battle object, the gamedata script etc.

Awesome idea. I'm hoping this is used as well.

No, you've forgotten something important. Read this:

Types of Artificial Intelligence (AI) - GeeksforGeeks

Understand it is the same as this:

12 Types of Malware + Examples That You Should Know | CrowdStrike

Geez. Maybe I was being too idealistic. I hope you can figure something out. 

Upload it to an archive website.

Because I think this game is awesome. I don't want it to get lost.

It's because the game undergoes an integer overflow as the code in the lecture file has not yet been added to the video series. The paid file includes the full lecture. I would recommend budgeting for it first, and upon buying it to use it as a back up file in case any modifications to your file goes wrong, for example adding a new feature. Name it something like "OriginalBackupSourceBattleSystem".  I'd rather have us learning than get frustrated waiting for lecture videos. 

Try checking for stray semicolons, as one of the for loops might be "empty" if a semicolon is in the wrong place.

Something else you could try is to see the original code and your code side by side to see if you've missed anything.

This may be closer to what you might be looking for.

Changing Palette of Sprites/Backgrounds Dynamically? : r/gamemaker (reddit.com)

Retro Palette Swapper by Pixelated Pope | GameMaker: Marketplace

Can I use a sprite when drawing a global variable? : r/gamemaker (reddit.com)

Looking to create rpg battle background like earth bound : r/gamemaker (reddit.com)

For now this is the closest I could find in terms of resources. I'll keep looking, though.

Try adding it to a struct maybe? Either that, or check to see if the variables have been mentioned properly. It looks like as if the error is based on an undefined variable.

Hope that helps you.

Well I really spoke too soon… Whoops. I’m a doofus.

As far as I know, it just includes the parts that have been released for the youtube series.

Would this help for ideas on how to expand a battle system?

1. More Engaging Turn-Based Combat in RPGs (youtube.com)

2.Top 5 JRPG Battle Systems (youtube.com)

For more complex backgrounds:

1: https://www.reddit.com/r/gamemaker/comments/e9tq81/looking_to_create_rpg_battle_background_like/?rdt=53881

2:
https://forum.gamemaker.io/index.php?threads/earthbound-battle-background-sprites.99273/

For more simple backgrounds:

1: https://manual.gamemaker.io/monthly/en/GameMaker_Language/GML_Overview/Variables/Global_Variables.htm

2: https://www.youtube.com/watch?v=imIDu84jIF0

I get this error as well. It also happens to me when I defeat all the enemies. Do you get that error too?

Pherhaps one could make a global variable for the background sprite/image index and change the sprite/image index based on the presence of an enemy type?