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.
412 lines
16 KiB
412 lines
16 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 © 2023 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<std::string>animations,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),animations(animations),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{
|
|
MonsterName+"_IDLE",
|
|
MonsterName+"_JUMP",
|
|
MonsterName+"_SPIT",
|
|
MonsterName+"_DIE",
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
for(int i=0;i<animations.size();i++){
|
|
std::string animationConfigName="";
|
|
std::string imgName="";
|
|
switch(i){
|
|
case 0:{
|
|
animationConfigName="Idle";
|
|
imgName="_IDLE";
|
|
}break;
|
|
case 1:{
|
|
animationConfigName="Jump";
|
|
imgName="_JUMP";
|
|
}break;
|
|
case 2:{
|
|
animationConfigName="Shoot";
|
|
imgName="_SPIT";
|
|
}break;
|
|
case 3:{
|
|
animationConfigName="Death";
|
|
imgName="_DIE";
|
|
}break;
|
|
}
|
|
Animate2D::Style style=Animate2D::Style::Repeat;
|
|
if(DATA["Monsters"][MonsterName][animationConfigName+"Animation"].GetString(2)=="Repeat"){
|
|
style=Animate2D::Style::Repeat;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName][animationConfigName+"Animation"].GetString(2)=="OneShot"){
|
|
style=Animate2D::Style::OneShot;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName][animationConfigName+"Animation"].GetString(2)=="PingPong"){
|
|
style=Animate2D::Style::PingPong;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName][animationConfigName+"Animation"].GetString(2)=="Reverse"){
|
|
style=Animate2D::Style::Reverse;
|
|
}else{
|
|
ERR(std::format("WARNING! Invalid Animation Style specified: {}",int(style)));
|
|
}
|
|
|
|
int frameCount=DATA["Monsters"][MonsterName][animationConfigName+"Animation"].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,MonsterName+imgName,i,AnimationData{float(DATA["Monsters"][MonsterName][animationConfigName+"Animation"].GetReal(1)),style});
|
|
}
|
|
|
|
//Add additional custom animations defined in the config.
|
|
int animationCounter=0;
|
|
int row=4;
|
|
while(DATA["Monsters"][MonsterName].HasProperty("ANIMATION["+std::to_string(animationCounter)+"]")){
|
|
animations.push_back(MonsterName+"ANIMATION["+std::to_string(animationCounter)+"]");
|
|
|
|
#pragma region Parse Animation Data
|
|
Animate2D::Style style;
|
|
if(DATA["Monsters"][MonsterName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="Repeat"){
|
|
style=Animate2D::Style::Repeat;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="OneShot"){
|
|
style=Animate2D::Style::OneShot;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="PingPong"){
|
|
style=Animate2D::Style::PingPong;
|
|
}else
|
|
if(DATA["Monsters"][MonsterName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="Reverse"){
|
|
style=Animate2D::Style::Reverse;
|
|
}else{
|
|
ERR(std::format("WARNING! Invalid Animation Style specified: {}",int(style)));
|
|
}
|
|
|
|
int frameCount=DATA["Monsters"][MonsterName]["ANIMATION["+std::to_string(animationCounter)+"]"].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,MonsterName+"ANIMATION["+std::to_string(animationCounter)+"]",row,AnimationData{float(DATA["Monsters"][MonsterName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetReal(1)),style});
|
|
#pragma endregion
|
|
|
|
row++;
|
|
animationCounter++;
|
|
}
|
|
|
|
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(),
|
|
animations,
|
|
drops,
|
|
float(DATA["Monsters"][MonsterName]["MoveSpd"].GetReal()),
|
|
float(DATA["Monsters"][MonsterName]["Size"].GetReal())/100,
|
|
strategyName,
|
|
DATA["Monsters"][MonsterName]["CollisionDmg"].GetInt()
|
|
);
|
|
|
|
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{
|
|
NPCName+"_IDLE",
|
|
NPCName+"_JUMP",
|
|
NPCName+"_SPIT",
|
|
NPCName+"_DIE",
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
for(int i=0;i<animations.size();i++){
|
|
std::string animationConfigName="";
|
|
std::string imgName="";
|
|
switch(i){
|
|
case 0:{
|
|
animationConfigName="Down";
|
|
imgName="_IDLE";
|
|
}break;
|
|
case 1:{
|
|
animationConfigName="Up";
|
|
imgName="_JUMP";
|
|
}break;
|
|
case 2:{
|
|
animationConfigName="Left";
|
|
imgName="_SPIT";
|
|
}break;
|
|
case 3:{
|
|
animationConfigName="Right";
|
|
imgName="_DIE";
|
|
}break;
|
|
}
|
|
Animate2D::Style style=Animate2D::Style::Repeat;
|
|
if(DATA["NPCs"][NPCName][animationConfigName+"Animation"].GetString(2)=="Repeat"){
|
|
style=Animate2D::Style::Repeat;
|
|
}else
|
|
if(DATA["NPCs"][NPCName][animationConfigName+"Animation"].GetString(2)=="OneShot"){
|
|
style=Animate2D::Style::OneShot;
|
|
}else
|
|
if(DATA["NPCs"][NPCName][animationConfigName+"Animation"].GetString(2)=="PingPong"){
|
|
style=Animate2D::Style::PingPong;
|
|
}else
|
|
if(DATA["NPCs"][NPCName][animationConfigName+"Animation"].GetString(2)=="Reverse"){
|
|
style=Animate2D::Style::Reverse;
|
|
}else{
|
|
ERR(std::format("WARNING! Invalid Animation Style specified: {}",int(style)));
|
|
}
|
|
|
|
int frameCount=DATA["NPCs"][NPCName][animationConfigName+"Animation"].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,NPCName+imgName,i,AnimationData{float(DATA["NPCs"][NPCName][animationConfigName+"Animation"].GetReal(1)),style});
|
|
}
|
|
|
|
//Add additional custom animations defined in the config.
|
|
int animationCounter=0;
|
|
int row=4;
|
|
while(DATA["NPCs"][NPCName].HasProperty("ANIMATION["+std::to_string(animationCounter)+"]")){
|
|
animations.push_back(NPCName+"ANIMATION["+std::to_string(animationCounter)+"]");
|
|
|
|
#pragma region Parse Animation Data
|
|
Animate2D::Style style;
|
|
if(DATA["NPCs"][NPCName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="Repeat"){
|
|
style=Animate2D::Style::Repeat;
|
|
}else
|
|
if(DATA["NPCs"][NPCName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="OneShot"){
|
|
style=Animate2D::Style::OneShot;
|
|
}else
|
|
if(DATA["NPCs"][NPCName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="PingPong"){
|
|
style=Animate2D::Style::PingPong;
|
|
}else
|
|
if(DATA["NPCs"][NPCName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetString(2)=="Reverse"){
|
|
style=Animate2D::Style::Reverse;
|
|
}else{
|
|
ERR(std::format("WARNING! Invalid Animation Style specified: {}",int(style)));
|
|
}
|
|
|
|
int frameCount=DATA["NPCs"][NPCName]["ANIMATION["+std::to_string(animationCounter)+"]"].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,NPCName+"ANIMATION["+std::to_string(animationCounter)+"]",row,AnimationData{float(DATA["NPCs"][NPCName]["ANIMATION["+std::to_string(animationCounter)+"]"].GetReal(1)),style});
|
|
#pragma endregion
|
|
|
|
row++;
|
|
animationCounter++;
|
|
}
|
|
|
|
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,animations,drops,moveSpd,size/100,strategyName,collisionDmg);
|
|
|
|
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 animations[IDLE];
|
|
}
|
|
std::string MonsterData::GetJumpAnimation(){
|
|
return animations[JUMP];
|
|
}
|
|
std::string MonsterData::GetShootAnimation(){
|
|
return animations[SHOOT];
|
|
}
|
|
std::string MonsterData::GetDeathAnimation(){
|
|
return animations[DEATH];
|
|
}
|
|
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;
|
|
} |