# pragma region License
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . com >
Redistribution and use in source and binary forms , with or without modification ,
are permitted provided that the following conditions are met :
1. Redistributions or derivations of source code must retain the above copyright
notice , this list of conditions and the following disclaimer .
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice . This list of conditions and the following disclaimer must be
reproduced in the documentation and / or other materials provided with the distribution .
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS " AND ANY
EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED
TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR
BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE .
Portions of this software are copyright © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "Monster.h"
# include "Animation.h"
# include "config.h"
# include "DEFINES.h"
# include "safemap.h"
# include "Item.h"
INCLUDE_DATA
INCLUDE_STRATEGY_DATA
INCLUDE_ANIMATION_DATA
INCLUDE_ITEM_DATA
std : : map < std : : string , MonsterData > MONSTER_DATA ;
MonsterData : : MonsterData ( )
: atk ( 0 ) , collisionDmg ( 0 ) , hp ( 0 ) , moveSpd ( 0 ) , size ( 0 ) , strategy ( " Run Towards " ) { }
MonsterData : : MonsterData ( std : : string name , int hp , int atk , const uint32_t xp , std : : vector < MonsterDropData > drops , float moveSpd , float size , std : : string strategy , int collisionDmg ) :
name ( name ) , hp ( hp ) , atk ( atk ) , xp ( xp ) , moveSpd ( moveSpd ) , size ( size ) , strategy ( strategy ) , dropData ( drops ) , collisionDmg ( collisionDmg ) { }
void MonsterData : : InitializeMonsterData ( ) {
for ( auto & [ key , size ] : DATA [ " Monsters " ] . GetKeys ( ) ) {
std : : string MonsterName = key ;
if ( MONSTER_DATA . count ( key ) ) {
ERR ( " WARNING! A monster with the name " < < key < < " already exists in the database! Duplicates are not allowed. " )
}
std : : vector < std : : string > animations ;
MonsterData : : imgs [ MonsterName ] = NEW Renderable ( ) ;
const rcode imgLoadResult = MonsterData : : imgs [ MonsterName ] - > Load ( " assets/monsters/ " + MonsterName + " .png " ) ;
if ( imgLoadResult ! = OK ) ERR ( std : : format ( " WARNING! Image loading for Monster {} failed with result {} " , MonsterName , int ( imgLoadResult ) ) ) ;
EventName hurtSound = " " ;
EventName deathSound = " " ;
EventName walkSound = " " ;
if ( DATA [ " Monsters " ] [ MonsterName ] . HasProperty ( " Hurt Sound " ) ) {
hurtSound = DATA [ " Monsters " ] [ MonsterName ] [ " Hurt Sound " ] . GetString ( ) ;
}
if ( DATA [ " Monsters " ] [ MonsterName ] . HasProperty ( " Death Sound " ) ) {
deathSound = DATA [ " Monsters " ] [ MonsterName ] [ " Death Sound " ] . GetString ( ) ;
}
if ( DATA [ " Monsters " ] [ MonsterName ] . HasProperty ( " Walk Sound " ) ) {
walkSound = DATA [ " Monsters " ] [ MonsterName ] [ " Walk Sound " ] . GetString ( ) ;
}
auto CreateHorizontalAnimationSequence = [ & ] ( Renderable & img , int frameCount , vf2d size , std : : string state , int row , AnimationData data = { } ) {
Animate2D : : FrameSequence anim ( data . frameDuration , data . style ) ;
for ( int i = 0 ; i < frameCount ; i + + ) {
anim . AddFrame ( { & img , { { int ( i * size . x ) , int ( row * size . y ) } , size } } ) ;
}
ANIMATION_DATA [ state ] = anim ;
} ;
if ( ! DATA [ " Monsters " ] [ MonsterName ] . HasProperty ( " Animations " ) ) ERR ( std : : format ( " WARNING! Could not find any animations to load for monster {}! Please check the Monsters.txt configuration file! " , MonsterName ) ) ;
if ( DATA [ " Monsters " ] [ MonsterName ] [ " Animations " ] . GetKeys ( ) . size ( ) < 4 ) ERR ( std : : format ( " WARNING! Monster {} does not have at least 4 animations. The animations should be defined in this order: a standing, walking, attack, and death animation " , MonsterName ) ) ;
for ( size_t animationRow = 0 ; auto & [ animationName , data ] : DATA [ " Monsters " ] [ MonsterName ] [ " Animations " ] . GetOrderedKeys ( ) ) {
Animate2D : : Style style = Animate2D : : Style : : Repeat ;
if ( data . GetString ( 2 ) = = " Repeat " ) {
style = Animate2D : : Style : : Repeat ;
} else
if ( data . GetString ( 2 ) = = " OneShot " ) {
style = Animate2D : : Style : : OneShot ;
} else
if ( data . GetString ( 2 ) = = " PingPong " ) {
style = Animate2D : : Style : : PingPong ;
} else
if ( data . GetString ( 2 ) = = " Reverse " ) {
style = Animate2D : : Style : : Reverse ;
} else {
ERR ( std : : format ( " WARNING! Invalid Animation Style specified: {} " , int ( style ) ) ) ;
}
int frameCount = data . GetInt ( 0 ) ;
vf2d frameSize = vf2d { float ( DATA [ " Monsters " ] [ MonsterName ] [ " SheetFrameSize " ] . GetInt ( 0 ) ) , float ( DATA [ " Monsters " ] [ MonsterName ] [ " SheetFrameSize " ] . GetInt ( 1 ) ) } ;
CreateHorizontalAnimationSequence ( * MonsterData : : imgs [ MonsterName ] , frameCount , frameSize , std : : format ( " {}_{} " , MonsterName , animationName ) , animationRow , AnimationData { float ( data . GetReal ( 1 ) ) , style } ) ;
animations . push_back ( animationName ) ;
animationRow + + ;
}
std : : vector < MonsterDropData > drops ;
//Add drop items to monster data from the config.
int dropDataCounter = 0 ;
while ( DATA [ " Monsters " ] [ MonsterName ] . HasProperty ( " DROP[ " + std : : to_string ( dropDataCounter ) + " ] " ) ) {
datafile drop = DATA [ " Monsters " ] [ MonsterName ] [ " DROP[ " + std : : to_string ( dropDataCounter ) + " ] " ] ;
if ( ! ITEM_DATA . count ( drop . GetString ( 0 ) ) ) {
ERR ( " Could not add drop " < < drop . GetString ( 0 ) < < " to " < < MonsterName < < " 's drop table! Item does not exist! " ) ;
}
drops . push_back ( MonsterDropData { drop . GetString ( 0 ) , float ( drop . GetReal ( 1 ) ) , drop . GetInt ( 2 ) , drop . GetInt ( 3 ) } ) ;
dropDataCounter + + ;
}
const std : : string & strategyName = DATA [ " Monsters " ] [ MonsterName ] [ " Strategy " ] . GetString ( ) ;
if ( ! STRATEGY_DATA . count ( strategyName ) ) {
ERR ( " WARNING! Strategy for " < < MonsterName < < " does not exist in strategy database! " ) ;
}
MonsterData monster (
MonsterName ,
DATA [ " Monsters " ] [ MonsterName ] [ " Health " ] . GetInt ( ) ,
DATA [ " Monsters " ] [ MonsterName ] [ " Attack " ] . GetInt ( ) ,
DATA [ " Monsters " ] [ MonsterName ] [ " XP " ] . GetInt ( ) ,
drops ,
float ( DATA [ " Monsters " ] [ MonsterName ] [ " MoveSpd " ] . GetReal ( ) ) ,
float ( DATA [ " Monsters " ] [ MonsterName ] [ " Size " ] . GetReal ( ) ) / 100 ,
strategyName ,
DATA [ " Monsters " ] [ MonsterName ] [ " CollisionDmg " ] . GetInt ( )
) ;
for ( size_t animationRow = 0 ; const std : : string & animationName : animations ) {
if ( ! monster . animations . insert ( animationName ) . second ) ERR ( std : : format ( " WARNING! The Animation {} for Monster {} already exists! Animations should have unique names! " , animationName , MonsterName ) ) ;
switch ( animationRow ) {
case 0 : monster . idleAnimation = animationName ; break ;
case 1 : monster . jumpAnimation = animationName ; break ;
case 2 : monster . shootAnimation = animationName ; break ;
case 3 : monster . deathAnimation = animationName ; break ;
}
animationRow + + ;
}
monster . hurtSound = hurtSound ;
monster . deathSound = deathSound ;
monster . walkSound = walkSound ;
MONSTER_DATA [ MonsterName ] = monster ;
}
}
void MonsterData : : InitializeNPCData ( ) {
for ( auto & [ key , dataSize ] : DATA [ " NPCs " ] . GetKeys ( ) ) {
std : : string NPCName = key ;
if ( MONSTER_DATA . count ( key ) ) {
ERR ( " WARNING! A monster with the name " < < key < < " already exists in the database! Duplicates are not allowed. " )
}
std : : vector < std : : string > animations ;
MonsterData : : imgs [ NPCName ] = NEW Renderable ( ) ;
const rcode imgLoadResult = MonsterData : : imgs [ NPCName ] - > Load ( " assets/npcs/ " + NPCName + " .png " ) ;
if ( imgLoadResult ! = OK ) ERR ( std : : format ( " WARNING! Image loading for NPC {} failed with result {} " , NPCName , int ( imgLoadResult ) ) ) ;
EventName hurtSound = " " ;
EventName deathSound = " " ;
EventName walkSound = " " ;
int health = 100 ;
int attack = 0 ;
int xp = 0 ;
float moveSpd = 100.f ;
float size = 100.f ;
int collisionDmg = 0 ;
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " Health " ) ) health = DATA [ " NPCs " ] [ NPCName ] [ " Health " ] . GetInt ( ) ;
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " Attack " ) ) attack = DATA [ " NPCs " ] [ NPCName ] [ " Attack " ] . GetInt ( ) ;
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " XP " ) ) xp = DATA [ " NPCs " ] [ NPCName ] [ " XP " ] . GetInt ( ) ;
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " MoveSpd " ) ) moveSpd = DATA [ " NPCs " ] [ NPCName ] [ " MoveSpd " ] . GetReal ( ) ;
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " Size " ) ) moveSpd = DATA [ " NPCs " ] [ NPCName ] [ " Size " ] . GetReal ( ) ;
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " CollisionDmg " ) ) collisionDmg = DATA [ " NPCs " ] [ NPCName ] [ " CollisionDmg " ] . GetInt ( ) ;
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " Hurt Sound " ) ) {
hurtSound = DATA [ " NPCs " ] [ NPCName ] [ " Hurt Sound " ] . GetString ( ) ;
}
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " Death Sound " ) ) {
deathSound = DATA [ " NPCs " ] [ NPCName ] [ " Death Sound " ] . GetString ( ) ;
}
if ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " Walk Sound " ) ) {
walkSound = DATA [ " NPCs " ] [ NPCName ] [ " Walk Sound " ] . GetString ( ) ;
}
auto CreateHorizontalAnimationSequence = [ & ] ( Renderable & img , int frameCount , vf2d size , std : : string state , int row , AnimationData data = { } ) {
Animate2D : : FrameSequence anim ( data . frameDuration , data . style ) ;
for ( int i = 0 ; i < frameCount ; i + + ) {
anim . AddFrame ( { & img , { { int ( i * size . x ) , int ( row * size . y ) } , size } } ) ;
}
ANIMATION_DATA [ state ] = anim ;
} ;
if ( ! DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " Animations " ) ) ERR ( std : : format ( " WARNING! Could not find any animations to load for monster {}! Please check the Monsters.txt configuration file! " , NPCName ) ) ;
if ( DATA [ " NPCs " ] [ NPCName ] [ " Animations " ] . GetKeys ( ) . size ( ) < 4 ) ERR ( std : : format ( " WARNING! Monster {} does not have at least 4 animations. The animations should be defined in this order: a standing, walking, attack, and death animation " , NPCName ) ) ;
for ( size_t animationRow = 0 ; auto & [ animationName , data ] : DATA [ " NPCs " ] [ NPCName ] [ " Animations " ] . GetOrderedKeys ( ) ) {
Animate2D : : Style style = Animate2D : : Style : : Repeat ;
if ( data . GetString ( 2 ) = = " Repeat " ) {
style = Animate2D : : Style : : Repeat ;
} else
if ( data . GetString ( 2 ) = = " OneShot " ) {
style = Animate2D : : Style : : OneShot ;
} else
if ( data . GetString ( 2 ) = = " PingPong " ) {
style = Animate2D : : Style : : PingPong ;
} else
if ( data . GetString ( 2 ) = = " Reverse " ) {
style = Animate2D : : Style : : Reverse ;
} else {
ERR ( std : : format ( " WARNING! Invalid Animation Style specified: {} " , int ( style ) ) ) ;
}
int frameCount = data . GetInt ( 0 ) ;
vf2d frameSize = vf2d { float ( DATA [ " NPCs " ] [ NPCName ] [ " SheetFrameSize " ] . GetInt ( 0 ) ) , float ( DATA [ " NPCs " ] [ NPCName ] [ " SheetFrameSize " ] . GetInt ( 1 ) ) } ;
CreateHorizontalAnimationSequence ( * MonsterData : : imgs [ NPCName ] , frameCount , frameSize , std : : format ( " {}_{} " , NPCName , animationName ) , animationRow , AnimationData { float ( data . GetReal ( 1 ) ) , style } ) ;
animations . push_back ( animationName ) ;
animationRow + + ;
}
std : : vector < MonsterDropData > drops ;
//Add drop items to monster data from the config.
int dropDataCounter = 0 ;
while ( DATA [ " NPCs " ] [ NPCName ] . HasProperty ( " DROP[ " + std : : to_string ( dropDataCounter ) + " ] " ) ) {
datafile drop = DATA [ " NPCs " ] [ NPCName ] [ " DROP[ " + std : : to_string ( dropDataCounter ) + " ] " ] ;
if ( ! ITEM_DATA . count ( drop . GetString ( 0 ) ) ) {
ERR ( " Could not add drop " < < drop . GetString ( 0 ) < < " to " < < NPCName < < " 's drop table! Item does not exist! " ) ;
}
drops . push_back ( MonsterDropData { drop . GetString ( 0 ) , float ( drop . GetReal ( 1 ) ) , drop . GetInt ( 2 ) , drop . GetInt ( 3 ) } ) ;
dropDataCounter + + ;
}
const std : : string & strategyName = DATA [ " NPCs " ] [ NPCName ] [ " Strategy " ] . GetString ( ) ;
if ( ! STRATEGY_DATA . count ( strategyName ) ) {
ERR ( " WARNING! Strategy for " < < NPCName < < " does not exist in strategy database! " ) ;
}
MonsterData monster ( NPCName , health , attack , xp , drops , moveSpd , size / 100 , strategyName , collisionDmg ) ;
for ( size_t animationRow = 0 ; const std : : string & animationName : animations ) {
if ( ! monster . animations . insert ( animationName ) . second ) ERR ( std : : format ( " WARNING! The Animation {} for Monster {} already exists! Animations should have unique names! " , animationName , NPCName ) ) ;
switch ( animationRow ) {
case 0 : monster . idleAnimation = animationName ; break ;
case 1 : monster . jumpAnimation = animationName ; break ;
case 2 : monster . shootAnimation = animationName ; break ;
case 3 : monster . deathAnimation = animationName ; break ;
}
animationRow + + ;
}
monster . hurtSound = hurtSound ;
monster . deathSound = deathSound ;
monster . walkSound = walkSound ;
monster . isNPC = true ; //If we read any data from this config file, it's definitely considered an NPC.
MONSTER_DATA [ NPCName ] = monster ;
}
}
int MonsterData : : GetHealth ( ) {
return hp ;
}
int MonsterData : : GetAttack ( ) {
return atk ;
}
float MonsterData : : GetMoveSpdMult ( ) {
return moveSpd ;
}
float MonsterData : : GetSizeMult ( ) {
return size ;
}
int MonsterData : : GetCollisionDmg ( ) {
return collisionDmg ;
}
const std : : string & MonsterData : : GetAIStrategy ( ) const {
return strategy ;
}
std : : string MonsterData : : GetDisplayName ( ) {
return name ;
}
std : : string MonsterData : : GetIdleAnimation ( ) {
return idleAnimation ;
}
std : : string MonsterData : : GetJumpAnimation ( ) {
return jumpAnimation ;
}
std : : string MonsterData : : GetShootAnimation ( ) {
return shootAnimation ;
}
std : : string MonsterData : : GetDeathAnimation ( ) {
return deathAnimation ;
}
const std : : vector < MonsterDropData > & MonsterData : : GetDropData ( ) {
return dropData ;
}
const EventName & MonsterData : : GetHurtSound ( ) {
return hurtSound ;
}
const EventName & MonsterData : : GetDeathSound ( ) {
return deathSound ;
}
const EventName & MonsterData : : GetWalkSound ( ) {
return walkSound ;
}