The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
14 KiB
335 lines
14 KiB
#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,size]:DATA["Monsters"][MonsterName]["Animations"]){
|
|
Animate2D::Style style=Animate2D::Style::Repeat;
|
|
if(DATA["Monsters"][MonsterName]["Animations"][animationName].GetString(2)=="Repeat"){
|
|
style=Animate2D::Style::Repeat;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName]["Animations"][animationName].GetString(2)=="OneShot"){
|
|
style=Animate2D::Style::OneShot;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName]["Animations"][animationName].GetString(2)=="PingPong"){
|
|
style=Animate2D::Style::PingPong;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName]["Animations"][animationName].GetString(2)=="Reverse"){
|
|
style=Animate2D::Style::Reverse;
|
|
}else{
|
|
ERR(std::format("WARNING! Invalid Animation Style specified: {}",int(style)));
|
|
}
|
|
|
|
int frameCount=DATA["Monsters"][MonsterName]["Animations"][animationName].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["Monsters"][MonsterName]["Animations"][animationName].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,size]:DATA["NPCs"][NPCName]["Animations"]){
|
|
Animate2D::Style style=Animate2D::Style::Repeat;
|
|
if(DATA["NPCs"][NPCName]["Animations"][animationName].GetString(2)=="Repeat"){
|
|
style=Animate2D::Style::Repeat;
|
|
}else
|
|
if(DATA["NPCs"][NPCName]["Animations"][animationName].GetString(2)=="OneShot"){
|
|
style=Animate2D::Style::OneShot;
|
|
}else
|
|
if(DATA["NPCs"][NPCName]["Animations"][animationName].GetString(2)=="PingPong"){
|
|
style=Animate2D::Style::PingPong;
|
|
}else
|
|
if(DATA["NPCs"][NPCName]["Animations"][animationName].GetString(2)=="Reverse"){
|
|
style=Animate2D::Style::Reverse;
|
|
}else{
|
|
ERR(std::format("WARNING! Invalid Animation Style specified: {}",int(style)));
|
|
}
|
|
|
|
int frameCount=DATA["NPCs"][NPCName]["Animations"][animationName].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["NPCs"][NPCName]["Animations"][animationName].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;
|
|
} |