Add in extra display labels for spells

Co-authored-by: sigonasr2 <sigonasr2@gmail.com>
master
sigonasr2 2 years ago
parent c3bbe09894
commit 5cfa2c4a03
  1. BIN
      C++ProjectTemplate
  2. 4
      assets/maps/map0
  3. 112
      main.cpp
  4. 28
      pixelGameEngine.h

Binary file not shown.

@ -54,6 +54,7 @@ OBJECT160.000000;128.000000;8
OBJECT192.000000;128.000000;8
OBJECT288.000000;128.000000;8
OBJECT313.000000;151.000000;8
OBJECT35.000000;158.000000;0
OBJECT313.000000;131.000000;10
OBJECT192.000000;160.000000;8
OBJECT160.000000;160.000000;8
@ -74,7 +75,6 @@ OBJECT160.000000;224.000000;8
OBJECT288.000000;224.000000;8
OBJECT224.000000;224.000000;8
OBJECT256.000000;224.000000;7
OBJECT76.000000;228.000000;0
OBJECT288.000000;256.000000;8
OBJECT256.000000;256.000000;8
OBJECT224.000000;256.000000;8
@ -82,5 +82,5 @@ OBJECT192.000000;256.000000;8
OBJECT160.000000;256.000000;8
ENCOUNTER64.000000;512.000000;90;2
ENCOUNTER64.000000;256.000000;50;1
ENCOUNTER192.000000;352.000000;25;1
ENCOUNTER192.000000;352.000000;100;1
ENCOUNTER128.000000;0.000000;25;2

@ -325,7 +325,7 @@ namespace Battle{
Move(std::string name,std::string desc,int baseDmg,int randomDmg,int PPCost,int range,int channelTime,bool friendly,std::array<int,4>composition,bool pctDamage=false,std::vector<std::pair<Property,int>> properties={})
:Move(name,desc,0,baseDmg,randomDmg,PPCost,range,channelTime,friendly,composition,pctDamage,properties){};
Move(std::string name,std::string desc,int grade,int baseDmg,int randomDmg,int PPCost,int range,int channelTime,bool friendly,std::array<int,4>composition,bool pctDamage=false,std::vector<std::pair<Property,int>> properties={})
:name(name),grade(grade),desc(desc),randomDmg(randomDmg),baseDmg(baseDmg),range(range),friendly(friendly),channelTime(channelTime),composition(composition),pctDamage(pctDamage),properties(properties){}
:name(name),grade(grade),PPCost(PPCost),desc(desc),randomDmg(randomDmg),baseDmg(baseDmg),range(range),friendly(friendly),channelTime(channelTime),composition(composition),pctDamage(pctDamage),properties(properties){}
};
}
@ -1522,9 +1522,27 @@ goes on a very long time, I hope you can understand this is only for testing pur
if (BATTLE_STATE==BattleState::POWER_SELECT||BATTLE_STATE==BattleState::GRADE_SELECT) {
SetDrawTarget(layer::INTERFACE);
DrawDialogBox({1,1},{(int)(WIDTH/2),HEIGHT/4},Pixel(70, 33, 105,128),Pixel(62, 54, 69,128),Pixel(185, 148, 255,128));
DrawDialogBox({WIDTH-(int)(WIDTH/2.5)-2,1},{(int)(WIDTH/2.5),HEIGHT/4},Pixel(70, 33, 105,128),Pixel(62, 54, 69,128),Pixel(185, 148, 255,128));
DrawDialogBox({WIDTH-WIDTH/3-2,HEIGHT/4+2},{(int)(WIDTH/6),HEIGHT/8},Pixel(70, 33, 105,128),Pixel(62, 54, 69,128),Pixel(185, 148, 255,128));
vd2d descBoxPos = {WIDTH-(int)(WIDTH/2.5)-2,1};
vi2d textStartingOffset = {4,4};
DrawDialogBox(descBoxPos,{(int)(WIDTH/2.5),HEIGHT/4},Pixel(70, 33, 105,128),Pixel(62, 54, 69,128),Pixel(185, 148, 255,128));
DrawStringPropDecal(descBoxPos+textStartingOffset,Wrap(BATTLE_MOVELIST_DISPLAY[POWER_SELECTION_CURSOR[-CURRENT_TURN-1]][0]->desc,{((int)(WIDTH/2.5-8))/0.8,(HEIGHT/4-8)/0.8},true),WHITE,{0.8,0.8});
vd2d ppCostBoxPos = {WIDTH-WIDTH/3-2,HEIGHT/4+2};
DrawDialogBox(ppCostBoxPos,{(int)(WIDTH/6),HEIGHT/8},Pixel(70, 33, 105,128),Pixel(62, 54, 69,128),Pixel(185, 148, 255,128));
DrawStringPropDecal(ppCostBoxPos+textStartingOffset,"PP Cost",WHITE,{0.7,0.8});
std::string label = std::to_string(BATTLE_MOVELIST_DISPLAY[POWER_SELECTION_CURSOR[-CURRENT_TURN-1]][POWER_GRADE_CURSOR[-CURRENT_TURN-1]]->PPCost);
DrawStringPropDecal({ppCostBoxPos.x+textStartingOffset.x+(WIDTH/6)-8-GetTextSizeProp(label).x*1.5,ppCostBoxPos.y+textStartingOffset.y+8},label,WHITE,{1.5,1.5});
vd2d damageBoxPos = {WIDTH-WIDTH/3+WIDTH/6-1,HEIGHT/4+2};
DrawStringPropDecal(damageBoxPos+textStartingOffset,"Damage",WHITE,{0.7,0.8});
label = (BATTLE_MOVELIST_DISPLAY[POWER_SELECTION_CURSOR[-CURRENT_TURN-1]][POWER_GRADE_CURSOR[-CURRENT_TURN-1]]->baseDmg!=0)?std::to_string(BATTLE_MOVELIST_DISPLAY[POWER_SELECTION_CURSOR[-CURRENT_TURN-1]][POWER_GRADE_CURSOR[-CURRENT_TURN-1]]->baseDmg+PARTY_MEMBER_STATS[-CURRENT_TURN-1]->baseAtk)+"~"+std::to_string(BATTLE_MOVELIST_DISPLAY[POWER_SELECTION_CURSOR[-CURRENT_TURN-1]][POWER_GRADE_CURSOR[-CURRENT_TURN-1]]->randomDmg+BATTLE_MOVELIST_DISPLAY[POWER_SELECTION_CURSOR[-CURRENT_TURN-1]][POWER_GRADE_CURSOR[-CURRENT_TURN-1]]->baseDmg+PARTY_MEMBER_STATS[-CURRENT_TURN-1]->baseAtk):"N/A";
DrawStringPropDecal({damageBoxPos.x+textStartingOffset.x+(WIDTH/6)-8-GetTextSizeProp(label).x*(std::min((double)((WIDTH/6)-8)/GetTextSizeProp(label).x,1.5)),damageBoxPos.y+textStartingOffset.y+8},label,WHITE,{std::min((double)((WIDTH/6)-8)/GetTextSizeProp(label).x,1.5),1.5});
DrawDialogBox({WIDTH-WIDTH/3+WIDTH/6-1,HEIGHT/4+2},{(int)(WIDTH/6),HEIGHT/8},Pixel(70, 33, 105,128),Pixel(62, 54, 69,128),Pixel(185, 148, 255,128));
vd2d rangeBoxPos = {WIDTH-WIDTH/3+WIDTH/6-1,HEIGHT/4+HEIGHT/8+3};
DrawStringPropDecal(rangeBoxPos+textStartingOffset,"Range",WHITE,{0.7,0.8});
label = std::to_string(BATTLE_MOVELIST_DISPLAY[POWER_SELECTION_CURSOR[-CURRENT_TURN-1]][POWER_GRADE_CURSOR[-CURRENT_TURN-1]]->range);
DrawStringPropDecal({rangeBoxPos.x+textStartingOffset.x+(WIDTH/6)-8-GetTextSizeProp(label).x*(std::min((double)((WIDTH/6)-8)/GetTextSizeProp(label).x,1.5)),rangeBoxPos.y+textStartingOffset.y+8},label,WHITE,{std::min((double)((WIDTH/6)-8)/GetTextSizeProp(label).x,1.5),1.5});
DrawDialogBox({WIDTH-WIDTH/3+WIDTH/6-1,HEIGHT/4+HEIGHT/8+3},{(int)(WIDTH/6),HEIGHT/8},Pixel(70, 33, 105,128),Pixel(62, 54, 69,128),Pixel(185, 148, 255,128));
int counter=0;
int displayLimit=0;
@ -1957,38 +1975,38 @@ goes on a very long time, I hope you can understand this is only for testing pur
MOVELIST[BattleMoveName::TESTMOVE1]=new Battle::Move("Test Move 1","An attack",30,5, 0,1,0,false,{0,0,0,0});
MOVELIST[BattleMoveName::TESTMOVE2]=new Battle::Move("Test Move 2","An attack",40,10, 0,1,0,false,{0,0,0,0});
MOVELIST[BattleMoveName::TESTMOVE3]=new Battle::Move("Test Move 3","An attack",25,5, 0,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_A]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",ALPHA,25,5, 4,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_B]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",BETA,25,5, 12,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_G]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",GAMMA,25,5, 28,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_O]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",OMEGA,25,5, 69,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_A]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",ALPHA,40,20, 4,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_B]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",BETA,80,20, 12,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_G]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",GAMMA,120,20, 28,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HAILSTORM_O]=new Battle::Move("Hailstorm","Causes heavy ice rocks to crash",OMEGA,210,50, 69,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HURRICANE_A]=new Battle::Move("Hurricane","Scatters seeds, causes heavy rains and winds",ALPHA,25,5, 7,14,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HURRICANE_B]=new Battle::Move("Hurricane","Scatters seeds, causes heavy rains and winds",BETA,25,5, 13,14,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HURRICANE_G]=new Battle::Move("Hurricane","Scatters seeds, causes heavy rains and winds",GAMMA,25,5, 25,20,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HURRICANE_O]=new Battle::Move("Hurricane","Scatters seeds, causes heavy rains and winds",OMEGA,25,5, 55,20,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_A]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",ALPHA,25,5, 10,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_B]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",BETA,25,5, 22,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_G]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",GAMMA,25,5, 47,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_O]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",OMEGA,25,5, 98,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_A]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",ALPHA,25,5, 4,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_B]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",BETA,25,5, 8,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_G]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",GAMMA,25,5, 12,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_O]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",OMEGA,25,5, 22,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_A]=new Battle::Move("PK Shield","Protects against physical attacks.",ALPHA,25,5, 12,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_B]=new Battle::Move("PK Shield","Protects against physical attacks.",BETA,25,5, 20,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_O]=new Battle::Move("PK Shield","Protects against physical attacks.",OMEGA,25,5, 59,4,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_S]=new Battle::Move("PK Shield","Protects against physical attacks.",SIGMA,25,5, 80,10,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_A]=new Battle::Move("PK Lifeup","Restores health.",ALPHA,25,5, 4,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_B]=new Battle::Move("PK Lifeup","Restores health.",BETA,25,5, 9,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_G]=new Battle::Move("PK Lifeup","Restores health.",GAMMA,25,5, 21,3,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_O]=new Battle::Move("PK Lifeup","Restores health.",OMEGA,25,5, 64,10,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_A]=new Battle::Move("PK Fun","A very fun barrage.",ALPHA,25,5, 15,12,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_B]=new Battle::Move("PK Fun","A very fun barrage.",BETA,25,5, 30,12,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_G]=new Battle::Move("PK Fun","A very fun barrage.",GAMMA,25,5, 45,20,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_O]=new Battle::Move("PK Fun","A very fun barrage.",OMEGA,25,5, 90,30,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_A]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",ALPHA,25,5, 6,4,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_B]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",BETA,25,5, 12,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_G]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",GAMMA,25,5, 20,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_O]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",OMEGA,25,5, 32,12,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HURRICANE_B]=new Battle::Move("Hurricane","Scatters seeds, causes heavy rains and winds",BETA,45,5, 13,14,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HURRICANE_G]=new Battle::Move("Hurricane","Scatters seeds, causes heavy rains and winds",GAMMA,75,10, 25,20,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::HURRICANE_O]=new Battle::Move("Hurricane","Scatters seeds, causes heavy rains and winds",OMEGA,125,20, 55,20,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_A]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",ALPHA,60,10, 10,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_B]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",BETA,110,30, 22,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_G]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",GAMMA,200,50, 47,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::METEORRAIN_O]=new Battle::Move("Meteor Rain","Causes fiery rocks to fall from the skies. Chance to burn trees.",OMEGA,390,60, 98,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_A]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",ALPHA,80,10, 4,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_B]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",BETA,120,20, 8,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_G]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",GAMMA,240,40, 12,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFREEZE_O]=new Battle::Move("PK Freeze","A powerful chilling attack causing frostbite and slow.",OMEGA,480,50, 22,1,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_A]=new Battle::Move("PK Shield","Protects against physical attacks.",ALPHA,0,0, 12,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_B]=new Battle::Move("PK Shield","Protects against physical attacks.",BETA,0,0, 20,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_O]=new Battle::Move("PK Shield","Protects against physical attacks.",OMEGA,0,0, 59,4,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKSHIELD_S]=new Battle::Move("PK Shield","Protects against physical attacks.",SIGMA,0,0, 80,10,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_A]=new Battle::Move("PK Lifeup","Restores a small amount of health.",ALPHA,80,10, 4,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_B]=new Battle::Move("PK Lifeup","Restores a moderate amount of health.",BETA,240,60, 9,1,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_G]=new Battle::Move("PK Lifeup","Restores a large amount of health.",GAMMA,400,50, 21,3,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKLIFEUP_O]=new Battle::Move("PK Lifeup","Restores a great amount of health to all allies.",OMEGA,800,100, 64,10,0,true,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_A]=new Battle::Move("PK Fun","A very fun barrage. Hits for large damage.",ALPHA,100,10, 15,12,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_B]=new Battle::Move("PK Fun","A very fun barrage. Hits for large damage.",BETA,240,40, 30,12,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_G]=new Battle::Move("PK Fun","A very fun barrage. Hits for large damage.",GAMMA,360,80, 45,20,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFUN_O]=new Battle::Move("PK Fun","A very fun barrage. Hits for large damage.",OMEGA,590,100, 90,30,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_A]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",ALPHA,60,20, 6,4,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_B]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",BETA,120,40, 12,6,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_G]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",GAMMA,190,50, 20,10,0,false,{0,0,20,0});
MOVELIST[BattleMoveName::PKFIRE_O]=new Battle::Move("PK Fire","Causes extreme heat to burn foes and scorch trees",OMEGA,360,100, 32,12,0,false,{0,0,20,0});
}
void SetupAnimations() {
@ -2531,6 +2549,32 @@ goes on a very long time, I hope you can understand this is only for testing pur
void ResetBattleState() {
CAMERA_WAIT_TIMER=0;
}
std::string Wrap(std::string str,vd2d size,bool proportional) {
int marker=0;
std::string newStr="";
while (marker<str.length()) {
newStr+=str[marker];
vd2d siz;
if (proportional) {
siz=GetTextSizeProp(newStr);
} else {
siz=GetTextSize(newStr);
}
if (siz.x>size.x) {
do {
if (newStr[newStr.length()-1]!=' ') {
marker--;
}
newStr.erase(newStr.end()-1);
} while (newStr[newStr.length()-1]!=' ');
newStr.erase(newStr.end()-1);
newStr+='\n';
}
marker++;
}
return newStr;
}
};

@ -1114,6 +1114,7 @@ namespace olc
std::string TextEntryGetString() const;
int32_t TextEntryGetCursor() const;
bool IsTextEntryEnabled() const;
void SetFPSDisplay(bool display);
@ -1177,6 +1178,7 @@ namespace olc
float fFrameTimer = 1.0f;
float fLastElapsed = 0.0f;
int nFrameCount = 0;
bool showFPS = true;
bool bSuspendTextureTransfer = false;
std::vector<LayerDesc> vLayers;
uint8_t nTargetLayer = 0;
@ -3209,6 +3211,9 @@ namespace olc
bool PixelGameEngine::IsTextEntryEnabled() const
{ return bTextEntryEnable; }
void PixelGameEngine::SetFPSDisplay(bool display)
{ showFPS=display; }
void PixelGameEngine::UpdateTextEntry()
{
@ -3546,16 +3551,21 @@ namespace olc
// Present Graphics to screen
renderer->DisplayFrame();
// Update Title Bar
fFrameTimer += fElapsedTime;
nFrameCount++;
if (fFrameTimer >= 1.0f)
{
nLastFPS = nFrameCount;
fFrameTimer -= 1.0f;
std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount);
if (showFPS) {
// Update Title Bar
fFrameTimer += fElapsedTime;
nFrameCount++;
if (fFrameTimer >= 1.0f)
{
nLastFPS = nFrameCount;
fFrameTimer -= 1.0f;
std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + " - FPS: " + std::to_string(nFrameCount);
platform->SetWindowTitle(sTitle);
nFrameCount = 0;
}
} else {
std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName;
platform->SetWindowTitle(sTitle);
nFrameCount = 0;
}
}

Loading…
Cancel
Save