Add player money functions. Implement Merchant transaction functions. Add Function Priming classes.
This commit is contained in:
parent
2b3c1ea1b4
commit
f1aa872717
@ -134,7 +134,7 @@ void Menu::InitializeCharacterMenuWindow(){
|
||||
ScrollableWindowComponent*equipList=Component<ScrollableWindowComponent>(data.component->parentMenu,"Equip List");
|
||||
equipList->RemoveAllComponents();
|
||||
for(int counter=0;Item&it:availableEquipment){
|
||||
Item&itemInvRef=Inventory::GetItem(it.Name());
|
||||
Item&itemInvRef=Inventory::GetItem(it.ActualName());
|
||||
auto equip=equipList->ADD("Equip Item "+std::to_string(counter),RowItemDisplay)({{2,2+counter*28.f},{120-15,28}},itemInvRef,
|
||||
[](MenuFuncData data){
|
||||
RowItemDisplay*comp=dynamic_cast<RowItemDisplay*>(data.component);
|
||||
|
@ -203,6 +203,10 @@ bool Crawler::OnUserCreate(){
|
||||
|
||||
ValidateGameStatus(); //Checks to make sure everything has been initialized properly.
|
||||
|
||||
Merchant::RandomizeTravelingMerchant();
|
||||
Merchant&myMerchant=Merchant::GetCurrentTravelingMerchant();
|
||||
const std::vector<Item>&itemsAvailable=myMerchant.GetShopItems();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1725,6 +1729,7 @@ void Crawler::ChangePlayerClass(Class cl){
|
||||
Ability itemAbility1=player->useItem1;
|
||||
Ability itemAbility2=player->useItem2;
|
||||
Ability itemAbility3=player->useItem3;
|
||||
uint32_t oldMoney=player->money;
|
||||
switch(cl){
|
||||
case WARRIOR:{
|
||||
player.reset(NEW Warrior(player.get()));
|
||||
@ -1750,6 +1755,7 @@ void Crawler::ChangePlayerClass(Class cl){
|
||||
player->SetBaseStat(ItemAttribute::attack,DATA.GetProperty(player->GetClassName()+".BaseAtk").GetInt());
|
||||
player->hpGrowthRate=float(DATA.GetProperty(player->GetClassName()+".HealthGrowthRate").GetReal());
|
||||
player->atkGrowthRate=float(DATA.GetProperty(player->GetClassName()+".AtkGrowthRate").GetReal());
|
||||
player->money=oldMoney;
|
||||
sig::Animation::SetupPlayerAnimations();
|
||||
GetPlayer()->UpdateIdleAnimation(DOWN);
|
||||
GetPlayer()->SetItem1UseFunc(itemAbility1);
|
||||
@ -2239,7 +2245,7 @@ void Crawler::SetLoadoutItem(int slot,std::string itemName){
|
||||
bool Crawler::UseLoadoutItem(int slot){
|
||||
if(slot<0||slot>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-1)+").");
|
||||
if(GetLoadoutItem(slot).Amt()>0){
|
||||
Inventory::UseItem(loadout[slot].Name());
|
||||
Inventory::UseItem(loadout[slot].ActualName());
|
||||
GetLoadoutItem(slot).OnUseAction();
|
||||
GetLoadoutItem(slot).amt--;
|
||||
return true;
|
||||
|
@ -316,6 +316,10 @@
|
||||
</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Error.h" />
|
||||
<ClInclude Include="FunctionPriming.h">
|
||||
<SubType>
|
||||
</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GameState.h" />
|
||||
<ClInclude Include="Item.h" />
|
||||
<ClInclude Include="ItemDrop.h">
|
||||
|
@ -309,6 +309,9 @@
|
||||
<ClInclude Include="Merchant.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FunctionPriming.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Player.cpp">
|
||||
|
@ -52,7 +52,7 @@ public:
|
||||
inline void OnEquipStatsUpdate()override{
|
||||
Item&equip=*Inventory::GetEquip(slot);
|
||||
if(!equip.IsBlank()){
|
||||
icon=equip.Decal();
|
||||
icon=const_cast<Decal*>(equip.Decal());
|
||||
SetItem(equip);
|
||||
}else{
|
||||
icon=nullptr;
|
||||
|
63
Crawler/FunctionPriming.h
Normal file
63
Crawler/FunctionPriming.h
Normal file
@ -0,0 +1,63 @@
|
||||
#pragma region License
|
||||
/*
|
||||
License (OLC-3)
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Copyright 2018 - 2022 OneLoneCoder.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
|
||||
#pragma once
|
||||
#include "Error.h"
|
||||
#include <format>
|
||||
using IT=std::string;
|
||||
|
||||
struct FunctionPrimingData{
|
||||
bool primed=false;
|
||||
std::string dependentFunction="";
|
||||
inline FunctionPrimingData(std::string dependentFunction)
|
||||
:dependentFunction(dependentFunction){}
|
||||
};
|
||||
|
||||
struct MerchantFunctionPrimingData:public FunctionPrimingData{
|
||||
IT item="";
|
||||
uint32_t amt=0;
|
||||
inline MerchantFunctionPrimingData(std::string dependentFunction)
|
||||
:FunctionPrimingData(dependentFunction){}
|
||||
virtual inline void Validate(IT item,uint32_t amt)const{
|
||||
if(!primed)ERR(std::format("WARNING! {} should be called before running this function! Priming Requirement!",dependentFunction));
|
||||
if(this->item!=item)ERR(std::format("WARNING! Primed items are not matching! {}!={}",this->item,item));
|
||||
if(this->amt!=amt)ERR(std::format("WARNING! Primed amounts are not matching! {}!={}",this->amt,amt));
|
||||
}
|
||||
inline bool operator=(bool rhs){
|
||||
return primed=rhs;
|
||||
}
|
||||
};
|
@ -75,7 +75,7 @@ void Menu::InitializeConsumableInventoryWindow(){
|
||||
}
|
||||
}
|
||||
button->selected=data.menu.I(A::LOADOUT_SLOT);
|
||||
data.game->SetLoadoutItem(button->selected,button->GetItem().Name());
|
||||
data.game->SetLoadoutItem(button->selected,button->GetItem().ActualName());
|
||||
return true;
|
||||
})END;
|
||||
|
||||
|
109
Crawler/Item.cpp
109
Crawler/Item.cpp
@ -102,6 +102,8 @@ void ItemInfo::InitializeItems(){
|
||||
std::vector<std::string> slot;
|
||||
float cooldownTime="Item.Item Cooldown Time"_F;
|
||||
std::vector<ItemAttribute>statValueList;
|
||||
uint32_t sellValue=0;
|
||||
uint32_t buyValue=0;
|
||||
for(auto&[itemKey,itemValue]:data[key].GetKeys()){
|
||||
std::string keyName=itemKey;
|
||||
if(keyName=="Description"){
|
||||
@ -131,6 +133,12 @@ void ItemInfo::InitializeItems(){
|
||||
}else
|
||||
if(keyName=="PartofSet"){
|
||||
setName=data[key][keyName].GetString();
|
||||
}
|
||||
if(keyName=="BuyValue"){
|
||||
buyValue=data[key][keyName].GetInt();
|
||||
}else
|
||||
if(keyName=="SellValue"){
|
||||
sellValue=data[key][keyName].GetInt();
|
||||
}else{ //THis is a custom override modifier for a script. NO-OP
|
||||
}
|
||||
}
|
||||
@ -162,6 +170,8 @@ void ItemInfo::InitializeItems(){
|
||||
it.cooldownTime=cooldownTime;
|
||||
it.slot=EquipSlot::NONE;
|
||||
it.set=setName;
|
||||
it.buyValue=buyValue;
|
||||
it.sellValue=sellValue;
|
||||
if(slot.size()>0){
|
||||
for(std::string&s:slot){
|
||||
if(!nameToEquipSlot.count(s))ERR("WARNING! Tried to add item "<<it.name<<" to slot "<<s<<" which doesn't exist!");
|
||||
@ -294,7 +304,7 @@ bool Inventory::RemoveItem(IT it,ITCategory inventory,uint32_t amt){
|
||||
}
|
||||
int count=0;
|
||||
for(Item&item:inv){
|
||||
if(item.Name()==it)break;
|
||||
if(item==it)break;
|
||||
count++;
|
||||
}
|
||||
#pragma endregion
|
||||
@ -376,21 +386,25 @@ bool Inventory::SwapItems(ITCategory itemCategory,uint32_t slot1,uint32_t slot2)
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t Item::Amt(){
|
||||
uint32_t Item::Amt()const{
|
||||
return amt;
|
||||
};
|
||||
std::string Item::Name(){
|
||||
const std::string&Item::ActualName()const{
|
||||
if(IsBlank())return "";
|
||||
std::string name=it->Name();
|
||||
return it->Name();
|
||||
};
|
||||
const std::string Item::DisplayName()const{
|
||||
if(IsBlank())return "";
|
||||
std::string name=ActualName();
|
||||
if(IsEquippable()&&EnhancementLevel()>0){
|
||||
name+=" [+"+std::to_string(EnhancementLevel())+"]";
|
||||
}
|
||||
return name;
|
||||
};
|
||||
bool Item::IsEquippable()const{
|
||||
}
|
||||
const bool Item::IsEquippable()const{
|
||||
return Category()=="Equipment"||Category()=="Accessories";
|
||||
}
|
||||
std::string Item::Description(CompactText compact){
|
||||
const std::string Item::Description(CompactText compact)const{
|
||||
std::string description=it->Description();
|
||||
if(IsEquippable()){
|
||||
description+='\n';
|
||||
@ -424,48 +438,52 @@ std::string Item::Description(CompactText compact){
|
||||
const ITCategory Item::Category()const{
|
||||
return it->Category();
|
||||
};
|
||||
::Decal*Item::Decal(){
|
||||
const::Decal*const Item::Decal()const{
|
||||
return it->Decal();
|
||||
};
|
||||
ItemScript&Item::OnUseAction(){
|
||||
const ItemScript&Item::OnUseAction()const{
|
||||
return it->OnUseAction();
|
||||
};
|
||||
|
||||
std::string ItemInfo::Name(){
|
||||
const std::string&ItemInfo::Name()const{
|
||||
return name;
|
||||
};
|
||||
std::string ItemInfo::Description(){
|
||||
const std::string&ItemInfo::Description()const{
|
||||
return description;
|
||||
};
|
||||
ITCategory ItemInfo::Category(){
|
||||
const ITCategory ItemInfo::Category()const{
|
||||
return category;
|
||||
};
|
||||
::Decal*ItemInfo::Decal(){
|
||||
const::Decal*const ItemInfo::Decal()const{
|
||||
return img;
|
||||
};
|
||||
ItemScript&ItemInfo::OnUseAction(){
|
||||
const ItemScript&ItemInfo::OnUseAction()const{
|
||||
return ITEM_SCRIPTS.at(useFunc);
|
||||
};
|
||||
|
||||
bool Item::IsBlank(){
|
||||
const bool Item::IsBlank()const{
|
||||
return amt==0||it==nullptr;
|
||||
}
|
||||
|
||||
void Inventory::Clear(ITCategory itemCategory){
|
||||
std::vector<Item>itemList=get(itemCategory); //We have to make a copy here because RemoveItem() will modify the list provided by get() inline.
|
||||
for(Item&item:itemList){
|
||||
size_t itemQuantity=GetItemCount(item.Name());//Normally we want to clear all the items that are actually in our inventory...But...
|
||||
size_t itemQuantity=GetItemCount(item.ActualName());//Normally we want to clear all the items that are actually in our inventory...But...
|
||||
if(itemCategory=="Monster Loot"||itemCategory=="Stage Loot"){//These do not affect the actual inventory, we just clear the lists.
|
||||
itemQuantity=item.Amt();
|
||||
}
|
||||
RemoveItem(item.Name(),itemCategory,uint32_t(itemQuantity));
|
||||
RemoveItem(item.ActualName(),itemCategory,uint32_t(itemQuantity));
|
||||
}
|
||||
}
|
||||
|
||||
bool Item::operator==(const Item&rhs)const{
|
||||
const bool Item::operator==(const Item&rhs)const{
|
||||
return it==rhs.it;
|
||||
}
|
||||
|
||||
const bool Item::operator==(const IT&rhs)const{
|
||||
return it->Name()==rhs;
|
||||
}
|
||||
|
||||
ItemOverlay::ItemOverlay(ItemInfo item)
|
||||
:it(item),width("ItemDrop.Item Drop Scale"_F*24+4+game->GetTextSizeProp(item.Name()).x*0.5f){
|
||||
xOffset=-width;
|
||||
@ -487,7 +505,7 @@ void ItemOverlay::Draw(){
|
||||
Pixel lightCol=Menu::GetCurrentTheme().GetButtonCol()*1.2f;
|
||||
game->GradientFillRectDecal(pos,{item.width,8},darkCol,darkCol,darkCol,lightCol);
|
||||
game->DrawRectDecal(pos,{item.width,8},Menu::GetCurrentTheme().GetHighlightCol());
|
||||
game->DrawDecal(pos,item.it.Decal(),{itemScale,itemScale});
|
||||
game->DrawDecal(pos,const_cast<Decal*>(item.it.Decal()),{itemScale,itemScale});
|
||||
game->DrawShadowStringPropDecal(pos+vf2d{itemScale*24+2,2},item.it.Name(),WHITE,BLACK,{0.5f,0.7f});
|
||||
counter++;
|
||||
}
|
||||
@ -498,11 +516,11 @@ void ItemOverlay::AddToItemOverlay(const ItemInfo&it){
|
||||
std::for_each(items.begin(),items.end(),[](ItemOverlay&it){it.ResetTimer();});
|
||||
}
|
||||
|
||||
float ItemInfo::CastTime(){
|
||||
const float ItemInfo::CastTime()const{
|
||||
return castTime;
|
||||
}
|
||||
|
||||
float ItemInfo::CooldownTime(){
|
||||
const float ItemInfo::CooldownTime()const{
|
||||
return cooldownTime;
|
||||
}
|
||||
|
||||
@ -510,11 +528,11 @@ void ItemOverlay::ResetTimer(){
|
||||
timer=0;
|
||||
}
|
||||
|
||||
float Item::CastTime(){
|
||||
const float Item::CastTime()const{
|
||||
return it->CastTime();
|
||||
}
|
||||
|
||||
float Item::CooldownTime(){
|
||||
const float Item::CooldownTime()const{
|
||||
return it->CooldownTime();
|
||||
}
|
||||
|
||||
@ -524,7 +542,7 @@ const Stats&EnhancementInfo::operator[](int level)const{
|
||||
|
||||
const std::optional<const ItemSet *const>ItemInfo::ItemSet()const{
|
||||
if(ItemSet::sets.count(set)){
|
||||
return &ItemSet::sets[set];
|
||||
return &ItemSet::sets.at(set);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
@ -560,17 +578,17 @@ EquipSlot Inventory::GetSlotEquippedIn(Item&it){
|
||||
EquipSlot slot=EquipSlot(i);
|
||||
Item*equip=GetEquip(slot);
|
||||
if(equip==nullptr)continue;
|
||||
if(equip->Name()==it.Name())return slot;
|
||||
if(equip->ActualName()==it.ActualName())return slot;
|
||||
}
|
||||
return EquipSlot::NONE;
|
||||
};
|
||||
Item*Inventory::GetEquip(EquipSlot slot){
|
||||
return Inventory::equipment[slot];
|
||||
}
|
||||
EquipSlot Item::GetEquipSlot(){
|
||||
const EquipSlot Item::GetEquipSlot()const{
|
||||
return it->Slot();
|
||||
}
|
||||
EquipSlot ItemInfo::Slot(){
|
||||
const EquipSlot ItemInfo::Slot()const{
|
||||
return slot;
|
||||
}
|
||||
|
||||
@ -600,7 +618,7 @@ void ItemInfo::InitializeSets(){
|
||||
}
|
||||
}
|
||||
|
||||
Stats ItemInfo::GetStats(int enhancementLevel){
|
||||
const Stats&ItemInfo::GetStats(int enhancementLevel)const{
|
||||
if(enhancement.size()<=enhancementLevel){
|
||||
return {};
|
||||
}
|
||||
@ -620,11 +638,11 @@ const std::optional<const ItemSet*const>Item::ItemSet()const{
|
||||
return it->ItemSet();
|
||||
};
|
||||
|
||||
uint8_t Item::EnhancementLevel()const{
|
||||
const uint8_t Item::EnhancementLevel()const{
|
||||
return enhancementLevel;
|
||||
};
|
||||
void Item::EnhanceItem(){
|
||||
if(enhancementLevel+1>"Item.Item Max Enhancement Level"_I)ERR("WARNING! Attempted to enhance "<<Name()<<" beyond the cap of "<<"Item.Item Max Enhancement Level"_I);
|
||||
if(enhancementLevel+1>"Item.Item Max Enhancement Level"_I)ERR("WARNING! Attempted to enhance "<<DisplayName()<<" beyond the cap of "<<"Item.Item Max Enhancement Level"_I);
|
||||
enhancementLevel++;
|
||||
};
|
||||
|
||||
@ -665,3 +683,34 @@ ItemInfo&ItemInfo::operator[](const IT&item){
|
||||
if(!ITEM_DATA.count(item))ERR("Item "<<std::quoted(item)<<" does not exist in the item database!");
|
||||
return ITEM_DATA[item];
|
||||
}
|
||||
|
||||
const uint32_t ItemInfo::GetBuyValue()const{
|
||||
return buyValue;
|
||||
}
|
||||
const uint32_t ItemInfo::GetSellValue()const{
|
||||
return sellValue;
|
||||
}
|
||||
const bool ItemInfo::CanBeSold()const{
|
||||
return GetSellValue()>0;
|
||||
}
|
||||
const bool ItemInfo::CanBePurchased()const{
|
||||
return GetBuyValue()>0;
|
||||
}
|
||||
|
||||
const uint32_t Item::BuyValue()const{
|
||||
return it->GetBuyValue();
|
||||
}
|
||||
const uint32_t Item::SellValue()const{
|
||||
return it->GetSellValue();
|
||||
}
|
||||
const bool Item::CanBeSold()const{
|
||||
return it->CanBeSold();
|
||||
}
|
||||
const bool Item::CanBePurchased()const{
|
||||
return it->CanBePurchased();
|
||||
}
|
||||
|
||||
|
||||
void Item::SetAmt(uint32_t newAmt){
|
||||
amt=newAmt;
|
||||
}
|
@ -44,6 +44,7 @@ All rights reserved.
|
||||
#include "AttributableStat.h"
|
||||
#include "BitwiseEnum.h"
|
||||
#include <optional>
|
||||
#include "Merchant.h"
|
||||
|
||||
class Crawler;
|
||||
class ItemInfo;
|
||||
@ -129,31 +130,42 @@ class Item{
|
||||
friend class Inventory;
|
||||
friend class Crawler;
|
||||
friend class Menu;
|
||||
friend void Merchant::PurchaseItem(IT item,uint32_t amt);
|
||||
friend void Merchant::SellItem(IT item,uint32_t amt);
|
||||
private:
|
||||
//The amount in the current item stack.
|
||||
uint32_t amt;
|
||||
uint8_t enhancementLevel;
|
||||
ItemInfo*it;
|
||||
void SetAmt(uint32_t newAmt);
|
||||
public:
|
||||
Item();
|
||||
Item(uint32_t amt,IT item,uint8_t enhancementLevel=0);
|
||||
uint32_t Amt();
|
||||
std::string Name();
|
||||
std::string Description(CompactText compact=COMPACT);
|
||||
uint32_t Amt()const;
|
||||
//Use this for places where the item name is used for item tracking. NOT for drawing. Hooks directly into item info's base item name.
|
||||
const std::string&ActualName()const;
|
||||
//Use for places where the item name is actually displayed. Provides modified text that shows additional information like enhancement levels.
|
||||
const std::string DisplayName()const;
|
||||
const std::string Description(CompactText compact=COMPACT)const;
|
||||
const ITCategory Category()const;
|
||||
EquipSlot GetEquipSlot();
|
||||
::Decal*Decal();
|
||||
const EquipSlot GetEquipSlot()const;
|
||||
const::Decal*const Decal()const;
|
||||
const Stats GetStats()const;
|
||||
ItemScript&OnUseAction();
|
||||
float CastTime();
|
||||
float CooldownTime();
|
||||
bool IsBlank();
|
||||
uint8_t EnhancementLevel()const;
|
||||
const ItemScript&OnUseAction()const;
|
||||
const float CastTime()const;
|
||||
const float CooldownTime()const;
|
||||
const bool IsBlank()const;
|
||||
const uint8_t EnhancementLevel()const;
|
||||
void EnhanceItem();
|
||||
static Item BLANK;
|
||||
bool operator==(const Item&rhs)const;
|
||||
const bool operator==(const Item&rhs)const;
|
||||
const bool operator==(const IT&rhs)const;
|
||||
const std::optional<const ::ItemSet *const>ItemSet()const;
|
||||
bool IsEquippable()const;
|
||||
const bool IsEquippable()const;
|
||||
const uint32_t BuyValue()const;
|
||||
const uint32_t SellValue()const;
|
||||
const bool CanBeSold()const;
|
||||
const bool CanBePurchased()const;
|
||||
};
|
||||
|
||||
class Inventory{
|
||||
@ -220,7 +232,8 @@ class ItemInfo{
|
||||
//Custom properties for this specific item's script.
|
||||
static utils::datafile NOPROPS;
|
||||
ItemProps customProps;
|
||||
|
||||
uint32_t buyValue=0;
|
||||
uint32_t sellValue=0;
|
||||
private:
|
||||
static void InitializeScripts();
|
||||
static void InitializeSets();
|
||||
@ -228,20 +241,24 @@ private:
|
||||
public:
|
||||
static void InitializeItems();
|
||||
ItemInfo();
|
||||
std::string Name();
|
||||
std::string Description();
|
||||
ITCategory Category();
|
||||
::Decal*Decal();
|
||||
const std::string&Name()const;
|
||||
const std::string&Description()const;
|
||||
const ITCategory Category()const;
|
||||
const::Decal*const Decal()const;
|
||||
/*
|
||||
For the useFunc, return true if the item can be used, false otherwise.
|
||||
*/
|
||||
ItemScript&OnUseAction();
|
||||
Stats GetStats(int enhancementLevel);
|
||||
float CastTime();
|
||||
float CooldownTime();
|
||||
EquipSlot Slot();
|
||||
const ItemScript&OnUseAction()const;
|
||||
const Stats&GetStats(int enhancementLevel)const;
|
||||
const float CastTime()const;
|
||||
const float CooldownTime()const;
|
||||
const EquipSlot Slot()const;
|
||||
const std::optional<const ::ItemSet *const>ItemSet()const;
|
||||
ItemInfo&operator[](const IT&item);
|
||||
const uint32_t GetBuyValue()const;
|
||||
const uint32_t GetSellValue()const;
|
||||
const bool CanBeSold()const;
|
||||
const bool CanBePurchased()const;
|
||||
};
|
||||
|
||||
class ItemOverlay{
|
||||
|
@ -78,9 +78,9 @@ void ItemDrop::Draw(){
|
||||
yOffset=sin((game->levelTime+randomSpinOffset)*3)*0.5f;
|
||||
}
|
||||
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},GFX["skill_overlay_icon_overlay.png"].Decal(),0,GFX["skill_overlay_icon_overlay.png"].Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},YELLOW);
|
||||
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},item->Decal(),0,item->Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{255,255,255,128});
|
||||
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},const_cast<Decal*>(item->Decal()),0,item->Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{255,255,255,128});
|
||||
game->SetDecalMode(DecalMode::ADDITIVE);
|
||||
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},item->Decal(),0,item->Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),128});
|
||||
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()+yOffset},const_cast<Decal*>(item->Decal()),0,item->Decal()->sprite->Size()/2,{"ItemDrop.Item Drop Scale"_F,"ItemDrop.Item Drop Scale"_F},{uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),uint8_t(abs(sin(game->levelTime*1.5)*255.f)),128});
|
||||
game->SetDecalMode(DecalMode::NORMAL);
|
||||
}
|
||||
|
||||
|
@ -60,12 +60,12 @@ private:
|
||||
public:
|
||||
int selected=-1; //0-2 representing which loadout slot this item consumes. -1 means not selected.
|
||||
inline MenuItemButton(geom2d::rect<float>rect,std::vector<Item>&invRef,int invIndex,MenuFunc onClick,MenuType itemDescriptionMenu,std::string itemNameLabelName,std::string itemDescriptionLabelName,IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
|
||||
:MenuIconButton(rect,invRef.size()>invIndex?invRef[invIndex].Decal():nullptr,onClick,attributes),invRef(invRef),inventoryIndex(invIndex),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
:MenuIconButton(rect,invRef.size()>invIndex?const_cast<Decal*>(invRef[invIndex].Decal()):nullptr,onClick,attributes),invRef(invRef),inventoryIndex(invIndex),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
draggable=false;
|
||||
valid=invRef.size()>invIndex;
|
||||
}
|
||||
inline MenuItemButton(geom2d::rect<float>rect,std::vector<Item>&invRef,int invIndex,MenuFunc onClick,MenuFunc onHover,MenuFunc onMouseOut,MenuType itemDescriptionMenu,std::string itemNameLabelName,std::string itemDescriptionLabelName,IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
|
||||
:MenuIconButton(rect,invRef.size()>invIndex?invRef[invIndex].Decal():nullptr,onClick,attributes),invRef(invRef),inventoryIndex(invIndex),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
:MenuIconButton(rect,invRef.size()>invIndex?const_cast<Decal*>(invRef[invIndex].Decal()):nullptr,onClick,attributes),invRef(invRef),inventoryIndex(invIndex),itemDescriptionMenu(itemDescriptionMenu),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
draggable=false;
|
||||
runHoverFunctions=true;
|
||||
valid=invRef.size()>invIndex;
|
||||
@ -73,12 +73,12 @@ public:
|
||||
SetMouseOutFunc(onMouseOut);
|
||||
}
|
||||
inline Item&GetItem(){
|
||||
return Inventory::GetItem(invRef.at(inventoryIndex).Name());
|
||||
return Inventory::GetItem(invRef.at(inventoryIndex).ActualName());
|
||||
}
|
||||
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
|
||||
inline bool UseItem(uint32_t amt=1){
|
||||
if(invRef.size()<=inventoryIndex)return false;
|
||||
return Inventory::UseItem(invRef.at(inventoryIndex).Name(),amt);
|
||||
return Inventory::UseItem(invRef.at(inventoryIndex).ActualName(),amt);
|
||||
}
|
||||
inline void SetCompactDescriptions(bool compact){
|
||||
if(compact)this->compact=COMPACT;
|
||||
@ -98,8 +98,8 @@ protected:
|
||||
std::string labelNameText;
|
||||
std::string labelDescriptionText;
|
||||
if(valid){
|
||||
icon=invRef[inventoryIndex].Decal();
|
||||
labelNameText=invRef[inventoryIndex].Name();
|
||||
icon=const_cast<Decal*>(invRef[inventoryIndex].Decal());
|
||||
labelNameText=invRef[inventoryIndex].DisplayName();
|
||||
labelDescriptionText=invRef[inventoryIndex].Description(compact);
|
||||
}else{
|
||||
icon=nullptr;
|
||||
@ -120,7 +120,7 @@ protected:
|
||||
}
|
||||
virtual inline void Update(Crawler*game)override{
|
||||
MenuIconButton::Update(game);
|
||||
valid=invRef.size()>inventoryIndex&&ITEM_DATA.count(invRef[inventoryIndex].Name());
|
||||
valid=invRef.size()>inventoryIndex&&ITEM_DATA.count(invRef[inventoryIndex].ActualName());
|
||||
|
||||
if(hovered){
|
||||
UpdateLabel();
|
||||
@ -132,7 +132,7 @@ protected:
|
||||
drawutil::DrawCrosshairDecalViewPort(window,{rect.pos,rect.size},0);
|
||||
}
|
||||
if(valid){
|
||||
int itemQuantity=Inventory::GetItemCount(invRef.at(inventoryIndex).Name()); //Normally we'd retrieve how many of this item we have from our inventory...However Monster Loot and Stage Loot inventories are special and hold their own inventory counts...
|
||||
int itemQuantity=Inventory::GetItemCount(invRef.at(inventoryIndex).ActualName()); //Normally we'd retrieve how many of this item we have from our inventory...However Monster Loot and Stage Loot inventories are special and hold their own inventory counts...
|
||||
if(&invRef==&Inventory::get("Monster Loot")||&invRef==&Inventory::get("Stage Loot")){
|
||||
itemQuantity=invRef.at(inventoryIndex).Amt(); //So the item quantity comes from the stack itself and not our main inventory.
|
||||
}
|
||||
|
@ -57,12 +57,12 @@ private:
|
||||
CompactText compact=COMPACT;
|
||||
public:
|
||||
inline MenuItemItemButton(geom2d::rect<float>rect,Item&itemRef,MenuType menuDest,MenuFunc onClick,std::string itemNameLabelName,std::string itemDescriptionLabelName,IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
|
||||
:MenuIconButton(rect,(!itemRef.IsBlank())?itemRef.Decal():nullptr,menuDest,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
:MenuIconButton(rect,(!itemRef.IsBlank())?const_cast<Decal*>(itemRef.Decal()):nullptr,menuDest,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
draggable=false;
|
||||
valid=!itemRef.IsBlank();
|
||||
}
|
||||
inline MenuItemItemButton(geom2d::rect<float>rect,Item&itemRef,MenuType menuDest,MenuFunc onClick,MenuFunc onHover,MenuFunc onMouseOut,std::string itemNameLabelName="",std::string itemDescriptionLabelName="",IconButtonAttr attributes=IconButtonAttr::SELECTABLE)
|
||||
:MenuIconButton(rect,(!itemRef.IsBlank())?itemRef.Decal():nullptr,menuDest,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
:MenuIconButton(rect,(!itemRef.IsBlank())?const_cast<Decal*>(itemRef.Decal()):nullptr,menuDest,onClick,attributes),itemRef(itemRef),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName){
|
||||
runHoverFunctions=true;
|
||||
draggable=false;
|
||||
valid=!itemRef.IsBlank();
|
||||
@ -87,7 +87,7 @@ public:
|
||||
icon=nullptr;
|
||||
return;
|
||||
}
|
||||
icon=itemRef.get().Decal();
|
||||
icon=const_cast<Decal*>(itemRef.get().Decal());
|
||||
}
|
||||
protected:
|
||||
virtual inline void OnMouseOut()override{
|
||||
@ -112,8 +112,8 @@ protected:
|
||||
return;
|
||||
}
|
||||
|
||||
icon=itemRef.get().Decal();
|
||||
labelNameText=itemRef.get().Name();
|
||||
icon=const_cast<Decal*>(itemRef.get().Decal());
|
||||
labelNameText=itemRef.get().DisplayName();
|
||||
labelDescriptionText=itemRef.get().Description(compact);
|
||||
if(itemNameLabelName!=""){
|
||||
Component<MenuLabel>(parentMenu,itemNameLabelName)->label=labelNameText;
|
||||
|
@ -37,10 +37,16 @@ All rights reserved.
|
||||
#pragma endregion
|
||||
|
||||
#include "Merchant.h"
|
||||
#include "Crawler.h"
|
||||
|
||||
INCLUDE_game
|
||||
|
||||
std::map<Chapter,std::vector<Merchant>>Merchant::merchants;
|
||||
MerchantFunctionPrimingData Merchant::purchaseFunctionPrimed("CanPurchaseItem()");
|
||||
MerchantFunctionPrimingData Merchant::sellFunctionPrimed("CanSellItem()");
|
||||
Merchant Merchant::travelingMerchant;
|
||||
|
||||
const Merchant&Merchant::GetRandomMerchant(Chapter chapter)const{
|
||||
const Merchant&Merchant::GetRandomMerchant(Chapter chapter){
|
||||
return merchants[chapter][rand()%(merchants[chapter].size()-1)];
|
||||
}
|
||||
|
||||
@ -57,8 +63,8 @@ Merchant&Merchant::AddMerchant(Chapter chapter){
|
||||
return merchants[chapter].back();
|
||||
}
|
||||
|
||||
void Merchant::AddItem(IT item,uint8_t enhancementLevel){
|
||||
shopItems.push_back(Item{1,item,enhancementLevel});
|
||||
void Merchant::AddItem(IT item,uint32_t amt,uint8_t enhancementLevel){
|
||||
shopItems.push_back(Item{amt,item,enhancementLevel});
|
||||
}
|
||||
|
||||
INCLUDE_DATA
|
||||
@ -82,7 +88,12 @@ void Merchant::Initialize(){
|
||||
std::string itemKey=std::format("Item[{}]",itemNumber);
|
||||
if(data.HasProperty(itemKey)){
|
||||
IT itemName=data[itemKey].GetString();
|
||||
newMerchant.AddItem(itemName);
|
||||
if(data[itemKey].GetValueCount()>1){
|
||||
int qty=data[itemKey].GetInt(1);
|
||||
newMerchant.AddItem(itemName,qty);
|
||||
}else{
|
||||
newMerchant.AddItem(itemName,INFINITE);
|
||||
}
|
||||
}else{
|
||||
ERR("Could not find item "<<itemNumber<<" in Merchant "<<merchantCount<<" of Chapter "<<chapter<<"!");
|
||||
}
|
||||
@ -96,3 +107,80 @@ void Merchant::Initialize(){
|
||||
std::cout<<std::format("Added {} merchants to Chapter {}",merchantCount,chapter)<<std::endl;
|
||||
}
|
||||
}
|
||||
bool Merchant::CanPurchaseItem(IT item,uint32_t amt)const{
|
||||
bool itemAvailable=false;
|
||||
const Item*foundItem=nullptr;
|
||||
for(const Item&it:shopItems){
|
||||
if(it==item&&it.Amt()>=amt&&it.CanBePurchased()){
|
||||
itemAvailable=true;
|
||||
foundItem=⁢
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
purchaseFunctionPrimed.amt=amt;
|
||||
purchaseFunctionPrimed.item=item;
|
||||
return purchaseFunctionPrimed=
|
||||
itemAvailable&&
|
||||
foundItem!=nullptr&&
|
||||
game->GetPlayer()->GetMoney()>=foundItem->BuyValue()*amt;
|
||||
};
|
||||
bool Merchant::CanSellItem(IT item,uint32_t amt)const{
|
||||
ItemInfo&it=ITEM_DATA[item];
|
||||
|
||||
sellFunctionPrimed.amt=amt;
|
||||
sellFunctionPrimed.item=item;
|
||||
return sellFunctionPrimed=
|
||||
it.CanBeSold()&&
|
||||
Inventory::GetItemCount(item)>=amt;
|
||||
};
|
||||
void Merchant::PurchaseItem(IT item,uint32_t amt){
|
||||
purchaseFunctionPrimed.Validate(item,amt);
|
||||
|
||||
uint32_t totalCost=0U;
|
||||
for(Item&it:shopItems){
|
||||
if(it==item){
|
||||
if(it.Amt()!=INFINITE){
|
||||
it.SetAmt(it.Amt()-amt);
|
||||
}
|
||||
totalCost=it.BuyValue()*amt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Inventory::AddItem(item,amt);
|
||||
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-totalCost);
|
||||
|
||||
purchaseFunctionPrimed=false;
|
||||
};
|
||||
void Merchant::SellItem(IT item,uint32_t amt){
|
||||
sellFunctionPrimed.Validate(item,amt);
|
||||
|
||||
uint32_t totalCost=0U;
|
||||
bool itemFound=false;
|
||||
for(Item&it:shopItems){
|
||||
if(it==item){
|
||||
if(it.Amt()!=INFINITE){
|
||||
it.SetAmt(it.Amt()+amt);
|
||||
}
|
||||
itemFound=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!itemFound){
|
||||
AddItem(item);
|
||||
}
|
||||
totalCost=ITEM_DATA[item].GetSellValue()*amt;
|
||||
|
||||
Inventory::RemoveItem(item,amt);
|
||||
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()+totalCost);
|
||||
|
||||
sellFunctionPrimed=false;
|
||||
};
|
||||
|
||||
void Merchant::RandomizeTravelingMerchant(){
|
||||
travelingMerchant=GetRandomMerchant(game->GetCurrentChapter());
|
||||
};
|
||||
Merchant&Merchant::GetCurrentTravelingMerchant(){
|
||||
return travelingMerchant;
|
||||
};
|
@ -35,21 +35,33 @@ Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
||||
All rights reserved.
|
||||
*/
|
||||
#pragma endregion
|
||||
|
||||
#include "Crawler.h"
|
||||
#pragma once
|
||||
#include "olcPixelGameEngine.h"
|
||||
#include "FunctionPriming.h"
|
||||
class Item;
|
||||
|
||||
using Chapter=int;
|
||||
using IT=std::string;
|
||||
|
||||
class Merchant{
|
||||
public:
|
||||
const Merchant&GetRandomMerchant(Chapter chapter)const;
|
||||
static void RandomizeTravelingMerchant();
|
||||
static Merchant&GetCurrentTravelingMerchant();
|
||||
const std::string&GetDisplayName()const;
|
||||
const std::vector<Item>&GetShopItems()const;
|
||||
void AddItem(IT item,uint8_t enhancementLevel=0U);
|
||||
void AddItem(IT item,uint32_t amt=1,uint8_t enhancementLevel=0U);
|
||||
bool CanPurchaseItem(IT item,uint32_t amt=1U)const;
|
||||
bool CanSellItem(IT item,uint32_t amt=1U)const;
|
||||
void PurchaseItem(IT item,uint32_t amt=1U);
|
||||
void SellItem(IT item,uint32_t amt=1U);
|
||||
public:
|
||||
static void Initialize();
|
||||
static Merchant&AddMerchant(Chapter chapter);
|
||||
private:
|
||||
static MerchantFunctionPrimingData purchaseFunctionPrimed;
|
||||
static MerchantFunctionPrimingData sellFunctionPrimed;
|
||||
static Merchant travelingMerchant;
|
||||
static const Merchant&GetRandomMerchant(Chapter chapter);
|
||||
static std::map<Chapter,std::vector<Merchant>>merchants;
|
||||
std::string displayName;
|
||||
std::vector<Item>shopItems;
|
||||
|
@ -909,3 +909,10 @@ void Player::SetBaseStat(ItemAttribute a,int val){
|
||||
const std::string&ItemSet::GetSetName()const{
|
||||
return name;
|
||||
}
|
||||
|
||||
uint32_t Player::GetMoney()const{
|
||||
return money;
|
||||
};
|
||||
void Player::SetMoney(uint32_t newMoney){
|
||||
money=newMoney;
|
||||
};
|
@ -115,6 +115,7 @@ private:
|
||||
Ability useItem1;
|
||||
Ability useItem2;
|
||||
Ability useItem3;
|
||||
uint32_t money=9999;
|
||||
protected:
|
||||
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
|
||||
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;
|
||||
@ -251,6 +252,9 @@ public:
|
||||
void SetItem3UseFunc(Ability a);
|
||||
|
||||
static InputGroup KEY_ABILITY1, KEY_ABILITY2, KEY_ABILITY3, KEY_ABILITY4, KEY_DEFENSIVE, KEY_ITEM1, KEY_ITEM2, KEY_ITEM3;
|
||||
|
||||
uint32_t GetMoney()const;
|
||||
void SetMoney(uint32_t newMoney);
|
||||
};
|
||||
|
||||
struct Warrior:Player{
|
||||
|
@ -57,10 +57,10 @@ public:
|
||||
MenuComponent::DrawDecal(window,focused);
|
||||
float scaleFactor=(rect.size.y-4)/24;
|
||||
vf2d iconSize=vf2d{scaleFactor,scaleFactor}*24.f;
|
||||
window.DrawDecal(rect.pos+vf2d{2,2},itemRef.get().Decal(),{scaleFactor,scaleFactor});
|
||||
window.DrawDecal(rect.pos+vf2d{2,2},const_cast<Decal*>(itemRef.get().Decal()),{scaleFactor,scaleFactor});
|
||||
window.DrawRectDecal(rect.pos+vf2d{2,2},iconSize);
|
||||
|
||||
std::string itemName=itemRef.get().Name();
|
||||
std::string itemName=itemRef.get().DisplayName();
|
||||
vf2d scaledSize={std::min(1.f,(rect.size.x-6-iconSize.x)/game->GetTextSizeProp(itemName).x),1};
|
||||
|
||||
window.DrawShadowStringPropDecal(rect.pos+vf2d{4,4}+vf2d{iconSize.x,iconSize.y/2-4},itemName,WHITE,BLACK,scaledSize);
|
||||
@ -109,7 +109,7 @@ public:
|
||||
std::string labelNameText;
|
||||
std::string labelDescriptionText;
|
||||
if(valid){
|
||||
labelNameText=itemRef.get().Name();
|
||||
labelNameText=itemRef.get().DisplayName();
|
||||
labelDescriptionText=itemRef.get().Description(compact);
|
||||
}else{
|
||||
labelNameText="";
|
||||
|
@ -39,7 +39,7 @@ All rights reserved.
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 2
|
||||
#define VERSION_PATCH 1
|
||||
#define VERSION_BUILD 4086
|
||||
#define VERSION_BUILD 4125
|
||||
|
||||
#define stringify(a) stringify_(a)
|
||||
#define stringify_(a) #a
|
||||
|
@ -5,25 +5,29 @@ Merchant
|
||||
Chapter1_A
|
||||
{
|
||||
DisplayName = Merchant
|
||||
Item[1] = Minor Health Potion
|
||||
Item[2] = Minor Mana Potion
|
||||
# Specify items this merchant sells. Add an optional quantity as a second argument. Not specifying a second argument will give the shop infinite supply.
|
||||
Item[1] = Minor Health Potion,5
|
||||
Item[2] = Minor Mana Potion,5
|
||||
}
|
||||
Chapter1_B
|
||||
{
|
||||
DisplayName = Merchant
|
||||
Item[1] = Minor Health Potion
|
||||
# Specify items this merchant sells. Add an optional quantity as a second argument. Not specifying a second argument will give the shop infinite supply.
|
||||
Item[1] = Minor Health Potion,5
|
||||
}
|
||||
Chapter1_C
|
||||
{
|
||||
DisplayName = Merchant
|
||||
Item[1] = Minor Mana Potion
|
||||
# Specify items this merchant sells. Add an optional quantity as a second argument. Not specifying a second argument will give the shop infinite supply.
|
||||
Item[1] = Minor Mana Potion,5
|
||||
}
|
||||
Chapter1_D
|
||||
{
|
||||
DisplayName = Merchant
|
||||
Item[1] = Minor Health Potion
|
||||
Item[2] = Minor Mana Potion
|
||||
Item[3] = Bandages
|
||||
# Specify items this merchant sells. Add an optional quantity as a second argument. Not specifying a second argument will give the shop infinite supply.
|
||||
Item[1] = Minor Health Potion,5
|
||||
Item[2] = Minor Mana Potion,5
|
||||
Item[3] = Bandages,5
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user