Patreon access for Sara's Turn Based Battle System - itch.io
This link should work.
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.
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
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.
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.
Would this help for ideas on how to expand a battle system?
For more complex backgrounds:
2:
https://forum.gamemaker.io/index.php?threads/earthbound-battle-background-sprites.99273/
For more simple backgrounds: