Refactored Map Data access so that other locations in code no longer had write abilities to the map data. Changed permanent exit zone spawning to a temporary. Fix equipment items being duplicated due to sorted inventory not being in sync with actual inventory.

pull/35/head
sigonasr2 12 months ago
parent 7162b151e4
commit 5c83a41a86
  1. 13
      Adventures in Lestoria/Adventures in Lestoria.tiled-project
  2. 36
      Adventures in Lestoria/AdventuresInLestoria.cpp
  3. 8
      Adventures in Lestoria/AdventuresInLestoria.h
  4. 2
      Adventures in Lestoria/EnvironmentalAudio.cpp
  5. 47
      Adventures in Lestoria/Item.cpp
  6. 4
      Adventures in Lestoria/Item.h
  7. 11
      Adventures in Lestoria/Monster.cpp
  8. 2
      Adventures in Lestoria/Pathfinding.cpp
  9. 4
      Adventures in Lestoria/Player.cpp
  10. 28
      Adventures in Lestoria/SaveFile.cpp
  11. 2
      Adventures in Lestoria/State_LevelComplete.cpp
  12. 38
      Adventures in Lestoria/TMXParser.h
  13. 2
      Adventures in Lestoria/TODO.txt
  14. 10
      Adventures in Lestoria/Test.cpp
  15. 12
      Adventures in Lestoria/Ursule.cpp
  16. 2
      Adventures in Lestoria/Version.h
  17. 3
      Adventures in Lestoria/assets/Campaigns/Boss_1_B.tmx
  18. 4
      Adventures in Lestoria/assets/Campaigns/Boss_1_v2.tmx
  19. 4
      Adventures in Lestoria/assets/Campaigns/World_Map.tmx
  20. 2
      Adventures in Lestoria/assets/config/Monsters.txt
  21. 15
      Adventures in Lestoria/assets/config/audio/bgm.txt
  22. 5
      Adventures in Lestoria/assets/config/audio/events.txt
  23. 5
      Adventures in Lestoria/assets/config/configuration.txt
  24. BIN
      Adventures in Lestoria/assets/sounds/fireplace.wav
  25. BIN
      Adventures in Lestoria/assets/sounds/ursule_dead.ogg

@ -52,6 +52,19 @@
],
"valuesAsFlags": false
},
{
"color": "#ffa45f5f",
"drawFill": true,
"id": 36,
"members": [
],
"name": "BossArena",
"type": "class",
"useAs": [
"property",
"object"
]
},
{
"color": "#ff290aa4",
"drawFill": true,

@ -806,14 +806,14 @@ void AiL::PopulateRenderLists(){
}
}
for(ZoneData&zone:MAP_DATA[GetCurrentLevel()].ZoneData["EndZone"]){
for(const ZoneData&zone:GetZones().at("EndZone")){
if(zone.isUpper){
upperEndZones.push_back(zone);
}else{
endZones.push_back(zone);
}
}
std::sort(monstersBeforeUpper.begin(),monstersBeforeUpper.end(),[](Monster*m1,Monster*m2){return m1->GetPos().y<m2->GetPos().y;});
std::sort(monstersBeforeLower.begin(),monstersBeforeLower.end(),[](Monster*m1,Monster*m2){return m1->GetPos().y<m2->GetPos().y;});
std::sort(monstersAfterUpper.begin(),monstersAfterUpper.end(),[](Monster*m1,Monster*m2){return m1->GetPos().y<m2->GetPos().y;});
@ -1744,6 +1744,7 @@ void AiL::LoadLevel(MapName map){
foregroundTileGroups.clear();
upperForegroundTileGroups.clear();
MONSTER_LIST.clear();
ZONE_LIST.clear();
GameEvent::events.clear();
worldColor=WHITE;
worldColorFunc=[&](vi2d pos){return game->worldColor;};
@ -1756,12 +1757,15 @@ void AiL::LoadLevel(MapName map){
totalBossEncounterMobs=0;
Inventory::Clear("Monster Loot");
Inventory::Clear("Stage Loot");
GetPlayer()->hp=GetPlayer()->GetMaxHealth();
GetPlayer()->mana=GetPlayer()->GetMaxMana();
GetPlayer()->SetState(State::NORMAL);
GetPlayer()->GetCastInfo()={};
GetPlayer()->ResetAccumulatedXP();
ZONE_LIST=game->MAP_DATA[game->GetCurrentLevel()].ZoneData;
#pragma region Reset Environmental Audio
for(EnvironmentalAudio&audio:MAP_DATA[map].environmentalAudioData){
audio.Deactivate();
@ -2338,6 +2342,12 @@ void AiL::InitializeLevels(){
InitializeLevel("map_path"_S+DATA["Levels"][key]["Map File"].GetString(),key);
}
std::set<std::string>cpNames;
for(ConnectionPoint&cp:State_OverworldMap::connections){
if(cpNames.count(cp.name)>0)ERR(std::format("WARNING! More than one connection point has the same name: {} THIS IS NOT ALLOWED!",cp.name));
cpNames.insert(cp.name);
}
for(ConnectionPoint&cp:State_OverworldMap::connections){
if(cp.levelDataExists)continue;
if(VisualNovel::storyLevelData.count(cp.map)){ //Visual novel story data for story levels.
@ -2960,4 +2970,26 @@ void AiL::SetWorldColor(Pixel worldCol){
const Pixel&AiL::GetWorldColor()const{
return worldColor;
}
const std::map<std::string,std::vector<::ZoneData>>&AiL::GetZones(const std::string_view mapName)const{
if(GetCurrentMapDisplayName()==mapName)return GetZones();
return MAP_DATA.at(std::string(mapName)).ZoneData;
}
const std::map<std::string,std::vector<::ZoneData>>&AiL::GetZones()const{
return ZONE_LIST;
}
void AiL::AddZone(const std::string_view zoneName,const ZoneData&zone){
if(ZONE_LIST.count(std::string(zoneName))==0)ERR(std::format("WARNING! Trying to add non-existent Zone Key {} to zone list of map {}. THIS IS NOT ALLOWED!",zoneName,std::string(GetCurrentMapName())));
ZONE_LIST[std::string(zoneName)].push_back(zone);
}
const std::string_view AiL::GetCurrentMapDisplayName()const{
return GetCurrentMap().GetMapDisplayName();
}
const uint8_t AiL::BossEncounterMobCount()const{
return totalBossEncounterMobs;
}

@ -142,6 +142,7 @@ private:
DynamicCounter manaCounter;
Pixel worldColor=WHITE;
std::function<Pixel(vi2d)>worldColorFunc=[](vi2d pos){return WHITE;};
std::map<std::string,std::vector<::ZoneData>>ZONE_LIST;
void ValidateGameStatus();
#ifndef __EMSCRIPTEN__
@ -223,11 +224,13 @@ public:
void DisplayBossEncounterInfo();
void BossDamageDealt(int damage);
void ReduceBossEncounterMobCount();
const uint8_t BossEncounterMobCount()const;
void InitializeGraphics();
void RenderVersionInfo();
const Map&GetCurrentMap()const;
const MapTag&GetCurrentMapData()const;
const MapName&GetCurrentMapName()const;
const std::string_view GetCurrentMapDisplayName()const;
int GetCurrentChapter();
void SetChapter(int chapter);
const std::weak_ptr<Item>GetLoadoutItem(int slot);
@ -248,6 +251,11 @@ public:
void SetWorldColorFunc(std::function<Pixel(vi2d)>func);
void SetWorldColor(Pixel worldCol);
const Pixel&GetWorldColor()const;
//Returns the zones in the current stage
const std::map<std::string,std::vector<::ZoneData>>&GetZones()const;
//Returns the zones of any given stage
const std::map<std::string,std::vector<::ZoneData>>&GetZones(const std::string_view mapName)const;
void AddZone(const std::string_view zoneName,const ZoneData&zone);
struct TileGroupData{
vi2d tilePos;

@ -79,7 +79,7 @@ void EnvironmentalAudio::Deactivate(){
activated=false;
}
void EnvironmentalAudio::UpdateEnvironmentalAudio(){
for(const EnvironmentalAudio&aud:game->GetCurrentMap().environmentalAudioData){
for(const EnvironmentalAudio&aud:game->GetCurrentMap().GetEnvironmentalAudio()){
EnvironmentalAudio&audio=const_cast<EnvironmentalAudio&>(aud);
audio.Update();
}

@ -409,6 +409,7 @@ std::weak_ptr<Item>Inventory::AddItem(IT it,uint32_t amt,bool monsterDrop){
std::shared_ptr<Item>newItem=(*_inventory.insert({it,std::make_shared<Item>(1,it)})).second;
newItem->RandomizeStats();
InsertIntoSortedInv(newItem);
InsertIntoStageInventoryCategory(newItem,monsterDrop);
itemPtr=newItem;
}
goto SkipAddingStackableItem;
@ -418,6 +419,7 @@ std::weak_ptr<Item>Inventory::AddItem(IT it,uint32_t amt,bool monsterDrop){
if(!_inventory.count(it)){
std::shared_ptr<Item>newItem=(*_inventory.insert({it,std::make_shared<Item>(amt,it)})).second;
InsertIntoSortedInv(newItem);
InsertIntoStageInventoryCategory(newItem,monsterDrop);
itemPtr=newItem;
}else{
auto inventory=_inventory.equal_range(it);
@ -429,7 +431,6 @@ std::weak_ptr<Item>Inventory::AddItem(IT it,uint32_t amt,bool monsterDrop){
}
SkipAddingStackableItem:
InsertIntoStageInventoryCategory(it,amt,monsterDrop);
return itemPtr;
}
@ -499,8 +500,12 @@ bool Inventory::RemoveItem(std::weak_ptr<Item>itemRef,ITCategory inventory,uint3
//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
if(!itemAmt)return false;
std::string itemName=itemRef.lock()->DisplayName();
if (amt>=itemAmt){
size_t invSize=inv.size();
inv.erase(inv.begin()+count); //Clears it from the detected sorted inventory as well!
if(invSize-1!=inv.size())ERR(std::format("WARNING! Did not properly erase {} from sorted inventory {}",itemName,inventory));
if(!eraseFromLootWindow){ //We must clear out the item AFTER we've updated context-sensitive inventories because they may be borrowing a ref from this structure!!!
_inventory.erase(itemRef.lock()->ActualName());
}
@ -511,6 +516,7 @@ bool Inventory::RemoveItem(std::weak_ptr<Item>itemRef,ITCategory inventory,uint3
}else{
if(itemRef.lock()->IsEquippable()){ //Since equipment doesn't stack, if we have more than one piece we have to still remove that piece.
bool found=false;
size_t erased=std::erase_if(_inventory,[&](const std::pair<const IT,std::shared_ptr<Item>>data){
if(!found&&data.second==itemRef){
found=true;
@ -519,7 +525,9 @@ bool Inventory::RemoveItem(std::weak_ptr<Item>itemRef,ITCategory inventory,uint3
return false;
});
if(erased!=1)ERR(std::format("Did not erase a single element, instead erased {} elements.",erased));
size_t invSize=inv.size();
inv.erase(inv.begin()+count); //Clears it from the detected sorted inventory as well!
if(invSize-1!=inv.size())ERR(std::format("WARNING! Did not properly erase {} from sorted inventory {}",itemName,inventory));
Menu::InventorySlotsUpdated(inventory);
return true;
}else{
@ -545,17 +553,22 @@ void Inventory::InsertIntoSortedInv(std::shared_ptr<Item>itemRef){
Menu::InventorySlotsUpdated(itemRef->Category());
}
void Inventory::InsertIntoStageInventoryCategory(IT item,uint32_t amt,bool monsterDrop){
void Inventory::InsertIntoStageInventoryCategory(std::shared_ptr<Item>itemRef,const bool monsterDrop){
std::string stageInventoryCategory="Stage Loot";
if(monsterDrop){
stageInventoryCategory="Monster Loot";
}
std::vector<std::shared_ptr<Item>>&inv=sortedInv.at(stageInventoryCategory);
std::vector<std::shared_ptr<Item>>::iterator it=std::find(inv.begin(),inv.end(),std::make_shared<Item>(amt,item)); //Uses operator== to compare if this item does exist in a stage/monster loot inventory already. We just make an in-place shared pointer of an item to compare with.
if(it!=inv.end()){
(*it)->amt+=amt;
if(itemRef->IsEquippable()){ //We cannot stack items! They are always individual.
inv.push_back(itemRef);
}else{
inv.push_back(std::make_shared<Item>(amt,item));
std::vector<std::shared_ptr<Item>>::iterator it=std::find(inv.begin(),inv.end(),itemRef); //Uses operator== to compare if this item does exist in a stage/monster loot inventory already. We just make an in-place shared pointer of an item to compare with.
if(it!=inv.end()){
(*it)->amt+=itemRef->Amt();
}else{
inv.push_back(itemRef);
}
}
Menu::InventorySlotsUpdated(stageInventoryCategory);
}
@ -692,8 +705,15 @@ const bool Item::IsBlank()const{
void Inventory::Clear(ITCategory itemCategory){
std::vector<std::shared_ptr<Item>>itemList=get(itemCategory); //We have to make a copy here because RemoveItem() will modify the list provided by get() inline.
for(std::shared_ptr<Item>&item:itemList){
RemoveItem(item,itemCategory,item->Amt());
if(itemCategory=="Monster Loot"||itemCategory=="Stage Loot"){
while(sortedInv[itemCategory].size()>0){
RemoveItem(sortedInv[itemCategory].front(),itemCategory,sortedInv[itemCategory].front()->Amt());
}
}else
{
for(std::shared_ptr<Item>&item:itemList){
RemoveItem(item,itemCategory,item->Amt());
}
}
}
@ -1101,8 +1121,7 @@ void Item::RandomizeStats(){
randomizedStats=it->RandomizeStats();
};
const Stats ItemInfo::GetMinStats()const{
return minStats;
const Stats ItemInfo::GetMinStats()const{return minStats;
}
const Stats ItemInfo::GetMaxStats()const{
return maxStats;
@ -1129,4 +1148,12 @@ const EventName&ItemInfo::UseSound()const{
}
const EventName&Item::UseSound()const{
return it->UseSound();
}
const std::vector<std::shared_ptr<Item>>Inventory::GetInventory(){
std::vector<std::shared_ptr<Item>>itemList;
for(size_t itemCount=0;auto&[itemName,item]:Inventory::_inventory){
itemList.push_back(item);
}
return itemList;
}

@ -247,6 +247,8 @@ public:
static EquipSlot GetSlotEquippedIn(const std::weak_ptr<Item>it);
static std::weak_ptr<Item>GetEquip(EquipSlot slot);
static const std::map<ItemSet,int>GetEquippedItemSets();
//Gets all items currently on inventory (Ignores Stage Loot and Monster Loot Inventories)
static const std::vector<std::shared_ptr<Item>>GetInventory();
static bool SwapItems(ITCategory itemCategory,uint32_t slot1,uint32_t slot2);
//Makes sure this is a valid category. Will error out if it doesn't exist! Use for ERROR HANDLING!
@ -256,7 +258,7 @@ public:
}
private:
static void InsertIntoSortedInv(std::shared_ptr<Item>itemRef);
static void InsertIntoStageInventoryCategory(IT item,uint32_t amt,bool monsterDrop);
static void InsertIntoStageInventoryCategory(std::shared_ptr<Item>itemRef,const bool monsterDrop);
static bool ExecuteAction(IT item);
static std::multimap<IT,std::shared_ptr<Item>>_inventory;
static std::map<EquipSlot,std::weak_ptr<Item>>equipment;

@ -669,6 +669,17 @@ void Monster::OnDeath(){
animation.ChangeState(internal_animState,GetDeathAnimationName());
if(isBoss){
game->ReduceBossEncounterMobCount();
if(game->BossEncounterMobCount()==0){
ZoneData exitRing{geom2d::rect<int>{vi2d{GetPos()-vf2d{"boss_spawn_ring_radius"_F,"boss_spawn_ring_radius"_F}},vi2d{"boss_spawn_ring_radius"_I*2,"boss_spawn_ring_radius"_I*2}},OnUpperLevel()};
const geom2d::rect<int>arenaBounds=game->GetZones().at("BossArena")[0].zone;
geom2d::rect<int>clampedArena{vi2d(arenaBounds.pos+"boss_spawn_ring_radius"_I),vi2d(arenaBounds.size-"boss_spawn_ring_radius"_I*2)};
exitRing.zone.pos.x=std::clamp(exitRing.zone.pos.x,clampedArena.pos.x,clampedArena.pos.x+clampedArena.size.x);
exitRing.zone.pos.y=std::clamp(exitRing.zone.pos.y,clampedArena.pos.y,clampedArena.pos.y+clampedArena.size.y);
game->AddZone("EndZone",exitRing); //Create a 144x144 ring around the dead boss.
}
}
if(hasStrategyDeathFunction){

@ -50,7 +50,7 @@ void Pathfinding::Initialize(){
for (int y = 0; y < game->GetCurrentMapData().height*24; y+=gridSpacing.y)
{
bool tileIsEmpty=true;
for(const LayerTag&layer:game->GetCurrentMap().LayerData){
for(const LayerTag&layer:game->GetCurrentMap().GetLayers()){
int tileID=layer.tiles[y/24][x/24]-1;
if(tileID!=-1){
tileIsEmpty=false;

@ -965,11 +965,11 @@ float Player::GetEndZoneStandTime(){
void Player::CheckEndZoneCollision(){
auto HasZoneData=[&](){
return game->MAP_DATA[game->GetCurrentLevel()].ZoneData.count("EndZone");
return game->GetZones().count("EndZone");
};
if(IsOutOfCombat()&&HasZoneData()){
for(ZoneData&zone:game->MAP_DATA[game->GetCurrentLevel()].ZoneData.at("EndZone")){
for(const ZoneData&zone:game->GetZones().at("EndZone")){
if(zone.isUpper==upperLevel&&geom2d::overlaps(GetPos(),zone.zone)){
endZoneStandTime+=game->GetElapsedTime();
if(endZoneStandTime>="Player.End Zone Wait Time"_F){

@ -80,22 +80,20 @@ const void SaveFile::SaveGame(){
std::filesystem::create_directories("save_file_path"_S);
utils::datafile saveFile;
utils::datafile::INITIAL_SETUP_COMPLETE=false;
for(size_t itemCount=0;auto&[cat,items]:Inventory::sortedInv){
for(std::shared_ptr<Item>&item:items){
saveFile["Items"][std::format("Item[{}]",itemCount)]["Amt"].SetInt(item->Amt());
saveFile["Items"][std::format("Item[{}]",itemCount)]["Enhancement Level"].SetInt(item->EnhancementLevel());
saveFile["Items"][std::format("Item[{}]",itemCount)]["Item Name"].SetString(item->ActualName());
saveFile["Items"][std::format("Item[{}]",itemCount)]["Equip Slot"].SetInt(int(Inventory::GetSlotEquippedIn(item)));
uint8_t loadoutSlotNumber=255;
for(int i=0;i<game->loadout.size();i++){
if(item==game->GetLoadoutItem(i)){loadoutSlotNumber=i;break;}
}
saveFile["Items"][std::format("Item[{}]",itemCount)]["LoadoutSlot"].SetInt(loadoutSlotNumber);
for(const auto&[attr,val]:item->RandomStats()){
saveFile["Items"][std::format("Item[{}]",itemCount)]["Attributes"][std::string(attr.ActualName())].SetReal(val);
}
itemCount++;
for(size_t itemCount=0;auto&item:Inventory::GetInventory()){
saveFile["Items"][std::format("Item[{}]",itemCount)]["Amt"].SetInt(item->Amt());
saveFile["Items"][std::format("Item[{}]",itemCount)]["Enhancement Level"].SetInt(item->EnhancementLevel());
saveFile["Items"][std::format("Item[{}]",itemCount)]["Item Name"].SetString(item->ActualName());
saveFile["Items"][std::format("Item[{}]",itemCount)]["Equip Slot"].SetInt(int(Inventory::GetSlotEquippedIn(item)));
uint8_t loadoutSlotNumber=255;
for(int i=0;i<game->loadout.size();i++){
if(item==game->GetLoadoutItem(i)){loadoutSlotNumber=i;break;}
}
saveFile["Items"][std::format("Item[{}]",itemCount)]["LoadoutSlot"].SetInt(loadoutSlotNumber);
for(const auto&[attr,val]:item->RandomStats()){
saveFile["Items"][std::format("Item[{}]",itemCount)]["Attributes"][std::string(attr.ActualName())].SetReal(val);
}
itemCount++;
}
saveFile["Player"]["Class"].SetString(game->GetPlayer()->GetClassName());
saveFile["Player"]["Level"].SetInt(game->GetPlayer()->Level());

@ -51,7 +51,7 @@ void State_LevelComplete::OnStateChange(GameState*prevState){
}
Component<MenuLabel>(MenuType::LEVEL_COMPLETE,"Level EXP Gain Outline")->SetLabel(std::format("+{} Exp",game->GetPlayer()->GetAccumulatedXP()));
game->GetPlayer()->AddXP(game->GetPlayer()->GetAccumulatedXP());
for(const ItemMapData&data:game->GetCurrentMap().stageLoot){
for(const ItemMapData&data:game->GetCurrentMap().GetStageLoot()){
uint8_t amountDiff=data.maxAmt-data.minAmt;
uint8_t randomAmt=data.maxAmt;
if(amountDiff>0){ //This check avoids division by zero.

@ -95,6 +95,9 @@ struct ZoneData{
};
struct Map{
friend class AiL;
friend class TMXParser;
private:
MapTag MapData;
std::string name;
Renderable*optimizedTile=nullptr;
@ -108,7 +111,15 @@ struct Map{
std::set<std::string>spawns;
std::map<int,SpawnerTag> SpawnerData; //Spawn groups have IDs, mobs associate which spawner they are tied to via this ID.
std::map<std::string,std::vector<::ZoneData>> ZoneData;
const std::map<std::string,std::vector<::ZoneData>>&GetZones()const;
public:
const MapTag&GetMapData()const;
const std::string_view GetMapType()const;
const std::vector<ItemMapData>&GetStageLoot()const;
const std::vector<LayerTag>&GetLayers()const;
const std::vector<EnvironmentalAudio>&GetEnvironmentalAudio()const;
const MapName&GetMapName()const;
const std::string_view GetMapDisplayName()const;
std::string FormatLayerData(std::ostream& os, std::vector<LayerTag>tiles);
std::string FormatSpawnerData(std::ostream& os, std::map<int,SpawnerTag>tiles);
friend std::ostream& operator << (std::ostream& os, Map& rhs);
@ -248,9 +259,30 @@ class TMXParser{
}
return displayStr;
}
const std::string_view Map::GetMapType()const{
return mapType;
}
const MapName&Map::GetMapName()const{
return name;
}
const MapTag&Map::GetMapData()const{
return MapData;
}
const std::vector<EnvironmentalAudio>&Map::GetEnvironmentalAudio()const{
return environmentalAudioData;
}
const std::map<std::string,std::vector<::ZoneData>>&Map::GetZones()const{
return ZoneData;
}
const std::vector<ItemMapData>&Map::GetStageLoot()const{
return stageLoot;
}
const std::vector<LayerTag>&Map::GetLayers()const{
return LayerData;
}
const std::string_view Map::GetMapDisplayName()const{
return name;
}
std::ostream& operator <<(std::ostream& os, std::vector<XMLTag>& rhs) {
for(XMLTag&tag:rhs){
os <<
@ -440,6 +472,12 @@ class TMXParser{
std::string accumulator="";
//Initialize these so that they are valid entries for zones.
parsedMapInfo.ZoneData["LowerZone"];
parsedMapInfo.ZoneData["UpperZone"];
parsedMapInfo.ZoneData["EndZone"];
parsedMapInfo.ZoneData["BossArena"];
while (f.good()&&!infiniteMap) {
std::string data;
f>>data;

@ -25,6 +25,8 @@ Settings Menu
- Implement escape menu during gameplay.
- If you leave a stage, the stage complete window still shows up, showing only the loot you obtained that session.
- No equip sounds for weapons?
January 31st
============

@ -54,14 +54,14 @@ void Test::is(std::string conditionStr,bool testResult){
void Test::RunMapTests(){
is("There are two LowerBridgeCollision zones in Campaign I-I",
game->MAP_DATA.at("CAMPAIGN_1_1").ZoneData.count("LowerBridgeCollision")
&&game->MAP_DATA.at("CAMPAIGN_1_1").ZoneData.at("LowerBridgeCollision").size()>=2);
game->GetZoneData("CAMPAIGN_1_1").count("LowerBridgeCollision")
&&game->GetZoneData("CAMPAIGN_1_1").at("LowerBridgeCollision").size()>=2);
for(auto&[key,value]:game->MAP_DATA){
is("A Map type has been selected for map "+key,
value.mapType!=""&&value.mapType!="Unspecified");
if(value.mapType=="Dungeon"){
value.GetMapType()!=""&&value.GetMapType()!="Unspecified");
if(value.GetMapType()=="Dungeon"){
is("There is an EndZone in Dungeon "+key,
value.ZoneData.count("EndZone"));
game->GetZones(key).count("EndZone"));
}
}
}

@ -127,7 +127,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
break;
}
}
vf2d mapCenter=game->GetCurrentMap().MapData.MapSize*vf2d{float(game->GetCurrentMap().MapData.tilewidth),float(game->GetCurrentMap().MapData.tileheight)}/2.0f;
vf2d mapCenter=game->GetCurrentMap().GetMapData().MapSize*vf2d{float(game->GetCurrentMap().GetMapData().tilewidth),float(game->GetCurrentMap().GetMapData().tileheight)}/2.0f;
float distToCenter=geom2d::line<float>(m.GetPos(),mapCenter).length();
if(distToCenter>4.0f){
m.targetAcquireTimer=20.f;
@ -204,7 +204,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
vi2d wispSize={ConfigIntArr("Phase 2.Wisp Size",0),ConfigIntArr("Phase 2.Wisp Size",1)};
float rowWidth=ConfigString(std::format("Wisp Pattern {}.Row[0]",wispPattern)).length()*wispSize.x; // Width of a wisp set in pixels.
float mapWidth=game->GetCurrentMap().MapData.MapSize.x*float(game->GetCurrentMap().MapData.tilewidth);
float mapWidth=game->GetCurrentMap().GetMapData().MapSize.x*float(game->GetCurrentMap().GetMapData().tilewidth);
int rowCount=Config(std::format("Wisp Pattern {}",wispPattern)).GetKeys().size();
for(float x=0;x<mapWidth;x+=wispSize.x){
for(int y=0;y<rowCount;y++){
@ -287,7 +287,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
break;
}
}
vf2d mapCenter=game->GetCurrentMap().MapData.MapSize*vf2d{float(game->GetCurrentMap().MapData.tilewidth),float(game->GetCurrentMap().MapData.tileheight)}/2.0f;
vf2d mapCenter=game->GetCurrentMap().GetMapData().MapSize*vf2d{float(game->GetCurrentMap().GetMapData().tilewidth),float(game->GetCurrentMap().GetMapData().tileheight)}/2.0f;
float distToCenter=geom2d::line<float>(m.GetPos(),mapCenter).length();
if(distToCenter>4.0f){
m.targetAcquireTimer=20.f;
@ -317,8 +317,8 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
m.AddBuff(COLLISION_KNOCKBACK_STRENGTH,10.f,ConfigFloat("Phase 3.Charge Attack Knockback Strength"));
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).upoint(2.0f);
//It's possible the charge forces the bear outside the map, so it will attempt to run forever on the clamped edges.
m.target.x=std::clamp(m.target.x,0.f,float(game->GetCurrentMap().MapData.width*game->GetCurrentMap().MapData.tilewidth));
m.target.y=std::clamp(m.target.y,0.f,float(game->GetCurrentMap().MapData.height*game->GetCurrentMap().MapData.tileheight));
m.target.x=std::clamp(m.target.x,0.f,float(game->GetCurrentMap().GetMapData().width*game->GetCurrentMap().GetMapData().tilewidth));
m.target.y=std::clamp(m.target.y,0.f,float(game->GetCurrentMap().GetMapData().height*game->GetCurrentMap().GetMapData().tileheight));
m.F(A::TARGET_TIMER)=ConfigFloat("Phase 3.Charge Max Run Time");
m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Phase 3.Charge Attack Cooldown");
m.PerformOtherAnimation(3);
@ -408,7 +408,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
vi2d wispSize={ConfigIntArr("Phase 4.Wisp Size",0),ConfigIntArr("Phase 4.Wisp Size",1)};
float rowWidth=ConfigString(std::format("Wisp Pattern {}.Row[0]",wispPattern)).length()*wispSize.x; // Width of a wisp set in pixels.
float mapWidth=game->GetCurrentMap().MapData.MapSize.x*float(game->GetCurrentMap().MapData.tilewidth);
float mapWidth=game->GetCurrentMap().GetMapData().MapSize.x*float(game->GetCurrentMap().GetMapData().tilewidth);
int rowCount=Config(std::format("Wisp Pattern {}",wispPattern)).GetKeys().size();
for(float x=0;x<mapWidth;x+=wispSize.x){
for(int y=0;y<rowCount;y++){

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 0
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 6459
#define VERSION_BUILD 6503
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="72" height="80" tilewidth="24" tileheight="24" infinite="0" nextlayerid="5" nextobjectid="6">
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="72" height="80" tilewidth="24" tileheight="24" infinite="0" nextlayerid="5" nextobjectid="7">
<properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty_boss"/>
@ -275,5 +275,6 @@
</properties>
<point/>
</object>
<object id="6" name="Boss Arena" type="BossArena" x="196" y="285" width="1339" height="1395"/>
</objectgroup>
</map>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="72" height="80" tilewidth="24" tileheight="24" infinite="0" nextlayerid="5" nextobjectid="6">
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="72" height="80" tilewidth="24" tileheight="24" infinite="0" nextlayerid="5" nextobjectid="9">
<properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty_boss"/>
@ -275,5 +275,7 @@
</properties>
<point/>
</object>
<object id="6" name="Boss Arena" type="BossArena" x="196" y="285" width="1339" height="1395"/>
<object id="7" x="29" y="444"/>
</objectgroup>
</map>

@ -669,7 +669,7 @@
<property name="Unlock Condition" propertytype="Level" value="BOSS_1"/>
</properties>
</object>
<object id="17" name="Stage VI" type="StagePlate" x="156.25" y="475.75" width="44" height="16">
<object id="17" name="Stage B-I" type="StagePlate" x="156.25" y="475.75" width="44" height="16">
<properties>
<property name="Connection 2 - East" type="object" value="18"/>
<property name="Connection 3 - South" type="object" value="0"/>
@ -679,7 +679,7 @@
<property name="Unlock Condition" propertytype="Level" value="CAMPAIGN_1_5"/>
</properties>
</object>
<object id="18" name="Boss I" type="StagePlate" x="192" y="516" width="32" height="24">
<object id="18" name="Boss B-I" type="StagePlate" x="192" y="516" width="32" height="24">
<properties>
<property name="Connection 1 - North" type="object" value="0"/>
<property name="Map" propertytype="Level" value="BOSS_1_B"/>

@ -381,7 +381,7 @@ Monsters
DROP[2] = Bear Claw,30%,1,1
Hurt Sound = Monster Hurt
Death Sound = Slime Dead
Death Sound = Ursule Dead
Walk Sound = Slime Walk
#Additional custom animations go down below. Start with ANIMATION[0] Order is:

@ -95,19 +95,20 @@ BGM
{
Track Name = Foresty Loop 1
channel[0]=loop2/foresty1_1_loop1_bass.ogg
channel[1]=loop2/foresty1_1_loop1_drums.ogg
channel[2]=loop2/foresty1_1_loop1_piano 1.ogg
channel[3]=loop2/foresty1_1_loop1_piano 2.ogg
channel[4]=loop2/foresty1_1_loop1_staccato.ogg
channel[5]=loop2/foresty1_1_loop1_strings.ogg
channel[0]=loop1/foresty1_1_loop1_bass.ogg
channel[1]=loop1/foresty1_1_loop1_drums.ogg
channel[2]=loop1/foresty1_1_loop1_flute.ogg
channel[3]=loop1/foresty1_1_loop1_piano 1.ogg
channel[4]=loop1/foresty1_1_loop1_piano 2.ogg
channel[5]=loop1/foresty1_1_loop1_strings.ogg
channel[6]=loop1/foresty1_1_loop1_xtra perc.ogg
# Transition time between one phase to the next.
Fade Time = 2.0
Events
{
Default Volume = 0%,70%,0%,70%,70%,0%
Default Volume = 0%,70%,0%,0%,70%,70%,0%
}
}
}

@ -144,6 +144,11 @@ Events
File[1] = slime_walk2.ogg, 10%
File[2] = slime_walk3.ogg, 10%
}
Ursule Dead
{
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ursule_dead.ogg, 100%
}
Ursule Phase Transition
{
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)

@ -154,4 +154,7 @@ water_reflection_scale_factor = 0.05
# The message displayed to the user about ID creation.
user_id_message = In order to save progress online, we ask that you provide a unique username to identify your save data with.
user_id_message2 = When loading a file, you will need this unique ID.
user_id_message2 = When loading a file, you will need this unique ID.
# How large the exit ring is for a boss when it's killed.
boss_spawn_ring_radius = 116
Loading…
Cancel
Save