|
|
|
#pragma region License
|
|
|
|
/*
|
|
|
|
License (OLC-3)
|
|
|
|
~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
|
|
be used to endorse or promote products derived from this software without specific
|
|
|
|
prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
SUCH DAMAGE.
|
|
|
|
|
|
|
|
Portions of this software are copyright © 2024 The FreeType
|
|
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
|
|
All rights reserved.
|
|
|
|
*/
|
|
|
|
#pragma endregion
|
|
|
|
#include "VisualNovel.h"
|
|
|
|
#include "GameState.h"
|
|
|
|
#include "AdventuresInLestoria.h"
|
|
|
|
#include <fstream>
|
|
|
|
#include "DEFINES.h"
|
|
|
|
#include "Unlock.h"
|
|
|
|
#include "Menu.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "SaveFile.h"
|
|
|
|
#include "State_OverworldMap.h"
|
|
|
|
|
|
|
|
INCLUDE_game
|
|
|
|
INCLUDE_GFX
|
|
|
|
|
|
|
|
VisualNovel VisualNovel::novel;
|
|
|
|
safemap<std::string,std::vector<std::unique_ptr<Command>>>VisualNovel::storyLevelData;
|
|
|
|
std::set<std::string>VisualNovel::graphicsToLoad;
|
|
|
|
Font VisualNovel::font,VisualNovel::narratorFont,VisualNovel::locationFont;
|
|
|
|
|
|
|
|
void VisualNovel::Initialize(){
|
|
|
|
font=Font("GFX_Prefix"_S+"dialog_font_size"_s[0]+".ttf","dialog_font_size"_i[1]);
|
|
|
|
narratorFont=Font("GFX_Prefix"_S+"narrator_font_size"_s[0]+".ttf","narrator_font_size"_i[1]);
|
|
|
|
locationFont=Font("GFX_Prefix"_S+"location_font_size"_s[0]+".ttf","location_font_size"_i[1]);
|
|
|
|
for(int chapter=1;chapter<=6;chapter++){
|
|
|
|
std::string chapterFilename="assets/"+"story_directory"_S+"Chapter "+std::to_string(chapter)+".txt";
|
|
|
|
std::ifstream file(chapterFilename);
|
|
|
|
if(!file)ERR("Failed to open file "<<chapterFilename)
|
|
|
|
std::string line;
|
|
|
|
std::string currentStory;
|
|
|
|
while(file.good()){
|
|
|
|
|
|
|
|
auto trim = [](std::string& s)
|
|
|
|
{
|
|
|
|
s.erase(0, s.find_first_not_of(" \t\n\r\f\v"));
|
|
|
|
s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
auto ReadCSVArgs=[](std::string text){
|
|
|
|
std::vector<std::string>args;
|
|
|
|
size_t counter=0;
|
|
|
|
while(text.find(',',counter)!=std::string::npos){
|
|
|
|
std::string arg=text.substr(counter,text.find(',',counter)-counter);
|
|
|
|
counter=text.find(',',counter)+1;
|
|
|
|
if(arg.find(',')!=std::string::npos)ERR("Found an erraneous comma inside parsed arg "<<arg<<". Inside of main text: "<<text);
|
|
|
|
args.push_back(arg);
|
|
|
|
}
|
|
|
|
std::string arg=text.substr(counter);
|
|
|
|
args.push_back(arg);
|
|
|
|
return args;
|
|
|
|
};
|
|
|
|
std::getline(file,line);
|
|
|
|
trim(line);
|
|
|
|
if(line.length()==0)continue; //It's a blank line, so we skip it.
|
|
|
|
|
|
|
|
switch(line[0]){
|
|
|
|
case '=':{ //Initializes a new story section.
|
|
|
|
currentStory=line.substr(3,line.find('=',3)-3);
|
|
|
|
if(currentStory.find('=')!=std::string::npos)ERR("Story name "<<currentStory<<" is an invalid name! Failed to load story.");
|
|
|
|
storyLevelData[currentStory];
|
|
|
|
}break;
|
|
|
|
case '{':{ //Start of a command.
|
|
|
|
auto&data=storyLevelData.at(currentStory);
|
|
|
|
|
|
|
|
auto AddImagesForLoading=[](std::vector<std::string>&arguments){
|
|
|
|
for(std::string&arg:arguments){
|
|
|
|
if(arg=="story_player_name"_S){
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+"Player_F.png");
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+"Wizard_F.png");
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+"Ranger_F.png");
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+"Trapper_F.png");
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+"Thief_F.png");
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+"Witch_F.png");
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+"Player_M.png");
|
|
|
|
}else{
|
|
|
|
graphicsToLoad.insert("character_image_location"_S+arg+".png");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t spacePos=line.find(' ');
|
|
|
|
std::vector<std::string>arguments;
|
|
|
|
|
|
|
|
if(spacePos!=std::string::npos){//There are arguments to parse...
|
|
|
|
size_t endingBracePos=line.find('}',spacePos+1);
|
|
|
|
if(endingBracePos==std::string::npos)ERR("Cannot parse arguments from "<<line<<": No closing ending brace found")
|
|
|
|
std::string args=line.substr(spacePos+1,endingBracePos-(spacePos+1));
|
|
|
|
if(line.size()==0||line[0]==' '||args.find('}')!=std::string::npos)ERR("Cannot parse args, found invalid tokens in "<<args)
|
|
|
|
arguments=ReadCSVArgs(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(line.find("{LOCATION")!=std::string::npos){//Location command
|
|
|
|
if(arguments.size()!=1)ERR("Arguments size is "<<arguments.size()<<". Expecting only 1 argument.")
|
|
|
|
data.push_back(std::make_unique<LocationCommand>(arguments[0]));
|
|
|
|
}else
|
|
|
|
if(line.find("{BACKGROUND")!=std::string::npos){//Background command
|
|
|
|
if(arguments.size()!=1)ERR("Arguments size is "<<arguments.size()<<". Expecting only 1 argument.")
|
|
|
|
graphicsToLoad.insert("story_background_image_location"_S+arguments[0]);
|
|
|
|
data.push_back(std::make_unique<BackgroundCommand>(arguments[0]));
|
|
|
|
}else
|
|
|
|
if(line.find("{LEFT")!=std::string::npos){//Left command
|
|
|
|
AddImagesForLoading(arguments);
|
|
|
|
data.push_back(std::make_unique<LeftCommand>(arguments));
|
|
|
|
}else
|
|
|
|
if(line.find("{RIGHT")!=std::string::npos){//Right command
|
|
|
|
AddImagesForLoading(arguments);
|
|
|
|
data.push_back(std::make_unique<RightCommand>(arguments));
|
|
|
|
}else
|
|
|
|
if(line.find("{PAUSE")!=std::string::npos){//Pause command
|
|
|
|
if(arguments.size()!=0)ERR("Arguments size is "<<arguments.size()<<". Expecting no arguments.")
|
|
|
|
data.push_back(std::make_unique<PauseCommand>());
|
|
|
|
}else
|
|
|
|
if(line.find("{AUDIOPITCH")!=std::string::npos){//Pause command
|
|
|
|
if(arguments.size()!=1)ERR("Arguments size is "<<arguments.size()<<". Expecting only 1 argument.")
|
|
|
|
data.push_back(std::make_unique<AudioPitchCommand>(arguments[0]));
|
|
|
|
}else
|
|
|
|
if(line.find("{BGM")!=std::string::npos){//Pause command
|
|
|
|
if(arguments.size()!=1)ERR("Arguments size is "<<arguments.size()<<". Expecting only 1 argument.")
|
|
|
|
data.push_back(std::make_unique<BGMCommand>(arguments[0]));
|
|
|
|
}else{
|
|
|
|
ERR("Unknown command "<<line<<". Could not parse!");
|
|
|
|
}
|
|
|
|
}break;
|
|
|
|
case '[':{
|
|
|
|
auto&data=storyLevelData.at(currentStory);
|
|
|
|
|
|
|
|
size_t endingBracePos=line.find(']');
|
|
|
|
if(endingBracePos==std::string::npos)ERR("Cannot parse arguments from "<<line<<": No closing ending bracket found")
|
|
|
|
std::string args=line.substr(1,endingBracePos-1);
|
|
|
|
if(args.find(']')!=std::string::npos)ERR("Cannot parse args, found invalid tokens in "<<args)
|
|
|
|
std::vector<std::string>arguments=ReadCSVArgs(args);
|
|
|
|
if(arguments.size()>2)ERR("Expecting a maximum of two arguments for parsed args in "<<args<<", got "<<arguments.size()<<" arguments instead.");
|
|
|
|
if(arguments.size()!=2){
|
|
|
|
data.push_back(std::make_unique<SpeakerCommand>(arguments[0]));
|
|
|
|
}else{
|
|
|
|
data.push_back(std::make_unique<SpeakerCommand>(arguments[0],arguments[1]));
|
|
|
|
}
|
|
|
|
}break;
|
|
|
|
default:{
|
|
|
|
auto&data=storyLevelData.at(currentStory);
|
|
|
|
|
|
|
|
data.push_back(std::make_unique<DialogCommand>(line));
|
|
|
|
}break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
storyLevelData.SetInitialized();
|
|
|
|
};
|
|
|
|
void VisualNovel::Reset(){
|
|
|
|
activeText=U"";
|
|
|
|
leftCharacters.clear();
|
|
|
|
rightCharacters.clear();
|
|
|
|
backgroundFilename=novel.prevBackgroundFilename="";
|
|
|
|
commands.clear();
|
|
|
|
commandIndex=0;
|
|
|
|
}
|
|
|
|
void VisualNovel::LoadVisualNovel(std::string storyLevelName){
|
|
|
|
novel.storyLevel=storyLevelName;
|
|
|
|
novel.Reset();
|
|
|
|
for(std::unique_ptr<Command>&command:storyLevelData.at(storyLevelName)){
|
|
|
|
novel.commands.push_back(command.get());
|
|
|
|
}
|
|
|
|
Audio::PlayBGM("story");
|
|
|
|
GameState::ChangeState(States::STORY,0.5f,10U);
|
|
|
|
novel.ExecuteNextCommand();
|
|
|
|
novel.prevTheme=Menu::GetCurrentTheme().GetThemeName();
|
|
|
|
Menu::themeSelection="Purple";
|
|
|
|
}
|
|
|
|
void VisualNovel::Update(){
|
|
|
|
Audio::SetBGMPitch(audioPitch);
|
|
|
|
if(transitionTime==0&&game->KEY_CONFIRM.Pressed()){
|
|
|
|
activeText=U"";
|
|
|
|
novel.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
locationDisplayTime=std::max(0.f,locationDisplayTime-game->GetElapsedTime());
|
|
|
|
transitionTime=std::max(0.f,transitionTime-game->GetElapsedTime());
|
|
|
|
textScrollTime=std::max(0.f,textScrollTime-game->GetElapsedTime());
|
|
|
|
if(backgroundScrollAmt<90.f){
|
|
|
|
backgroundScrollAmt=std::min(90.f,backgroundScrollAmt+backgroundScrollSpd*game->GetElapsedTime());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void VisualNovel::ExecuteNextCommand(){
|
|
|
|
if(commandIndex<commands.size()){
|
|
|
|
commandIndex++;
|
|
|
|
commands[size_t(commandIndex-1)]->Execute(novel);
|
|
|
|
}else{
|
|
|
|
if(GameState::STATE==GameState::states[States::DIALOG]){
|
|
|
|
Reset();
|
|
|
|
GameState::STATE=GameState::states[States::GAME_RUN];
|
|
|
|
}else{
|
|
|
|
if(game->GetCurrentMapName()=="NPCs.Greg.Camp Notification Unlock Condition"_S&&
|
|
|
|
!Unlock::IsUnlocked("NPCs.Greg.Camp Notification Unlock Condition"_S))State_OverworldMap::ConnectionPointFromString("HUB").value()->ResetVisitedFlag();
|
|
|
|
Unlock::UnlockCurrentMap();
|
|
|
|
Menu::themeSelection=novel.prevTheme;
|
|
|
|
GameState::ChangeState(States::OVERWORLD_MAP,0.5f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void VisualNovel::Draw(const uint8_t backgroundAlpha){
|
|
|
|
if(backgroundFilename!=""){
|
|
|
|
float alpha=backgroundAlpha/255.f;
|
|
|
|
if(transitionTime>0){
|
|
|
|
alpha=alpha*util::lerp(0,1,1-(transitionTime/maxTransitionTime));
|
|
|
|
}
|
|
|
|
if(prevBackgroundFilename!=""){
|
|
|
|
game->DrawDecal({0,-prevBackgroundScrollAmt},GFX["story_background_image_location"_S+prevBackgroundFilename].Decal(),{1.f,1.f},{255,255,255,backgroundAlpha});
|
|
|
|
}
|
|
|
|
game->DrawDecal({0,-backgroundScrollAmt},GFX["story_background_image_location"_S+backgroundFilename].Decal(),{1,1},{255,255,255,uint8_t(255*alpha)});
|
|
|
|
}else{
|
|
|
|
game->FillRectDecal({0,0},game->GetScreenSize(),{255,255,255,backgroundAlpha});
|
|
|
|
}
|
|
|
|
for(int i=leftCharacters.size()-1;i>=0;i--){
|
|
|
|
//Start 72 from the bottom.
|
|
|
|
std::u32string character(leftCharacters[i].begin(),leftCharacters[i].end());
|
|
|
|
Pixel fadeColor=WHITE;
|
|
|
|
if(character!=actualSpeakerName)fadeColor={128,128,128,255};
|
|
|
|
float yScaling=168.f/GFX[GetCharacterImage(character)].Sprite()->height;
|
|
|
|
|
|
|
|
game->DrawRotatedDecal(vi2d{0,game->GetScreenSize().y}-vi2d{-i*64-36,152},GFX[GetCharacterImage(character)].Decal(),0,GFX[GetCharacterImage(character)].Sprite()->Size()/2,vf2d{yScaling,yScaling},fadeColor);
|
|
|
|
}
|
|
|
|
for(int i=rightCharacters.size()-1;i>=0;i--){
|
|
|
|
//Start 72 from the bottom.
|
|
|
|
std::u32string character(rightCharacters[i].begin(),rightCharacters[i].end());
|
|
|
|
Pixel fadeColor=WHITE;
|
|
|
|
if(character!=actualSpeakerName)fadeColor={128,128,128,255};
|
|
|
|
float yScaling=168.f/GFX[GetCharacterImage(character)].Sprite()->height;
|
|
|
|
|
|
|
|
game->DrawRotatedDecal(game->GetScreenSize()-vi2d{i*64+36,152},GFX[GetCharacterImage(character)].Decal(),0,GFX[GetCharacterImage(character)].Sprite()->Size()/2,vf2d{-yScaling,yScaling},fadeColor);
|
|
|
|
}
|
|
|
|
if(locationDisplayTime>0){
|
|
|
|
std::u32string locationStr=std::u32string(locationDisplayText.begin(),locationDisplayText.end());
|
|
|
|
FontRect textSize=locationFont.GetStringBounds(locationStr);
|
|
|
|
textSize.offset*=2;
|
|
|
|
textSize.size*=2;
|
|
|
|
game->FillRectDecal(game->GetScreenSize()/2-textSize.size/2-vi2d{4,4}+textSize.offset/2,textSize.size+vi2d{8,8},BLACK);
|
|
|
|
game->DrawRectDecal(game->GetScreenSize()/2-textSize.size/2-vi2d{4,4}+textSize.offset/2,textSize.size+vi2d{8,8},WHITE);
|
|
|
|
game->DrawShadowStringDecal(locationFont,game->GetScreenSize()/2-textSize.size/2+textSize.offset/2,locationStr,WHITE,VERY_DARK_BLUE,{2.f,2.f});
|
|
|
|
}
|
|
|
|
if(activeText.length()>0){
|
|
|
|
vf2d nameDisplayPos={24.f,game->GetScreenSize().y-60.f};
|
|
|
|
vf2d nameDisplayWindowSize={48.f,-12.f};
|
|
|
|
if(speakerDisplayName.length()>0){
|
|
|
|
if(std::find_if(rightCharacters.begin(),rightCharacters.end(),[&](std::string&rightCharacter){
|
|
|
|
return rightCharacter==std::string(speakerDisplayName.begin(),speakerDisplayName.end());
|
|
|
|
})!=rightCharacters.end()){ //Found the character on the right side, so the box should also be drawn on the right side.
|
|
|
|
nameDisplayPos={game->ScreenWidth()-24.f-nameDisplayWindowSize.x,game->GetScreenSize().y-60.f};
|
|
|
|
}
|
|
|
|
Menu::DrawThemedWindow(nameDisplayPos,nameDisplayWindowSize);
|
|
|
|
}
|
|
|
|
std::u32string displayedName=speakerDisplayName;
|
|
|
|
std::u32string saveFileName;
|
|
|
|
saveFileName.assign(SaveFile::GetSaveFileName().begin(),SaveFile::GetSaveFileName().end());
|
|
|
|
if(displayedName==U"You"){
|
|
|
|
displayedName=saveFileName;
|
|
|
|
}
|
|
|
|
vf2d dialogDisplayPos={24.f,game->GetScreenSize().y-48.f};
|
|
|
|
vf2d dialogDisplaySize={game->GetScreenSize().x-48.f,20.f};
|
|
|
|
Menu::DrawThemedWindow(dialogDisplayPos,dialogDisplaySize);
|
|
|
|
FontRect dialogTextSize=font.GetStringBounds(activeText);
|
|
|
|
if(dialogTextSize.size.x>0&&dialogTextSize.size.y>0){
|
|
|
|
if(displayedName.length()>0){
|
|
|
|
FontRect speakerTextSize=font.GetStringBounds(displayedName);
|
|
|
|
game->DrawShadowStringDecal(font,nameDisplayPos-vf2d{10,7}+(nameDisplayWindowSize+vf2d{24,0})/2-speakerTextSize.size/2+speakerTextSize.offset/2,displayedName);
|
|
|
|
game->DrawShadowStringDecal(font,dialogDisplayPos-vf2d{10,6}+dialogTextSize.offset,activeText);
|
|
|
|
}else{
|
|
|
|
game->DrawDropShadowStringDecal(narratorFont,dialogDisplayPos-vf2d{10,6}+dialogTextSize.offset,activeText,{190,190,220});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
float yOffset=util::lerp(dialogDisplaySize.y+12,-8,textScrollTime/maxTextScrollTime);
|
|
|
|
game->DrawPolygonDecal(
|
|
|
|
Menu::GetPatchPart(1,1).Decal(),
|
|
|
|
{dialogDisplayPos-vf2d{12,-yOffset},dialogDisplayPos+vf2d{-12,dialogDisplaySize.y+12},dialogDisplayPos+dialogDisplaySize+vf2d{12,12},dialogDisplayPos+vf2d{dialogDisplaySize.x+12,yOffset}},
|
|
|
|
{{0,0},{0,1},{1,1},{1,0}},
|
|
|
|
{{255,255,255,240},{255,255,255,255},{255,255,255,255},{255,255,255,240}});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::string VisualNovel::GetCharacterImage(std::u32string name){
|
|
|
|
if(name==U"You"){ //Assume we are using female player avatar for now!
|
|
|
|
return std::format("{}{}_F.png","character_image_location"_S,game->GetPlayer()->GetClassName());
|
|
|
|
}
|
|
|
|
return "character_image_location"_S+std::string(name.begin(),name.end())+".png";
|
|
|
|
}
|
|
|
|
|
|
|
|
VisualNovel::VisualNovel(){}
|
|
|
|
|
|
|
|
Command::Command(){}
|
|
|
|
|
|
|
|
void LocationCommand::Execute(VisualNovel&vn){
|
|
|
|
vn.locationDisplayTime=5.f;
|
|
|
|
vn.locationDisplayText=location;
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
LocationCommand::LocationCommand(std::string location)
|
|
|
|
:location(location){}
|
|
|
|
CommandType::CommandType LocationCommand::GetType(){return CommandType::LOCATION;}
|
|
|
|
|
|
|
|
void BackgroundCommand::Execute(VisualNovel&vn){
|
|
|
|
vn.prevBackgroundFilename=vn.backgroundFilename;
|
|
|
|
vn.backgroundFilename=backgroundFilename;
|
|
|
|
vn.transitionTime=2.0f;
|
|
|
|
vn.prevBackgroundScrollAmt=vn.backgroundScrollAmt;
|
|
|
|
vn.backgroundScrollAmt=0.f;
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
BackgroundCommand::BackgroundCommand(std::string backgroundFilename)
|
|
|
|
:backgroundFilename(backgroundFilename){}
|
|
|
|
CommandType::CommandType BackgroundCommand::GetType(){return CommandType::BACKGROUND;}
|
|
|
|
|
|
|
|
void LeftCommand::Execute(VisualNovel&vn){
|
|
|
|
vn.leftCharacters=characters;
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
LeftCommand::LeftCommand(std::vector<std::string>characters)
|
|
|
|
:characters(characters){}
|
|
|
|
CommandType::CommandType LeftCommand::GetType(){return CommandType::LEFT;}
|
|
|
|
|
|
|
|
void RightCommand::Execute(VisualNovel&vn){
|
|
|
|
vn.rightCharacters=characters;
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
RightCommand::RightCommand(std::vector<std::string>characters)
|
|
|
|
:characters(characters){}
|
|
|
|
CommandType::CommandType RightCommand::GetType(){return CommandType::RIGHT;}
|
|
|
|
|
|
|
|
void SpeakerCommand::Execute(VisualNovel&vn){
|
|
|
|
if(displayedName=="You"){
|
|
|
|
vn.speakerDisplayName.assign(SaveFile::GetSaveFileName().begin(),SaveFile::GetSaveFileName().end());
|
|
|
|
}
|
|
|
|
vn.speakerDisplayName.assign(displayedName.begin(),displayedName.end());
|
|
|
|
vn.actualSpeakerName.assign(actualSpeakerName.begin(),actualSpeakerName.end());
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
SpeakerCommand::SpeakerCommand(std::string speaker)
|
|
|
|
:displayedName(speaker),actualSpeakerName(speaker){}
|
|
|
|
SpeakerCommand::SpeakerCommand(std::string displayedName,std::string speaker)
|
|
|
|
:displayedName(displayedName),actualSpeakerName(speaker){}
|
|
|
|
CommandType::CommandType SpeakerCommand::GetType(){return CommandType::SPEAKER;}
|
|
|
|
|
|
|
|
void DialogCommand::Execute(VisualNovel&vn){
|
|
|
|
if(dialog.size()<=0)return;
|
|
|
|
#pragma region Process "[You]" in Dialog.
|
|
|
|
std::u32string _dialog;
|
|
|
|
_dialog.assign(dialog.begin(),dialog.end());
|
|
|
|
std::u32string processedDialog=_dialog;
|
|
|
|
size_t playerNameReplacePos=processedDialog.find(U"[You]");
|
|
|
|
while(playerNameReplacePos!=std::u32string::npos){
|
|
|
|
std::u32string saveFileName;
|
|
|
|
saveFileName.assign(SaveFile::GetSaveFileName().begin(),SaveFile::GetSaveFileName().end());
|
|
|
|
processedDialog=processedDialog.replace(playerNameReplacePos,U"[You]"s.length(),saveFileName);
|
|
|
|
playerNameReplacePos=processedDialog.find(U"[You]");
|
|
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
vn.textScrollTime=VisualNovel::maxTextScrollTime;
|
|
|
|
bool mustDisplay=vn.activeText.length()==0;
|
|
|
|
Font*displayFont=&VisualNovel::font;
|
|
|
|
if(vn.actualSpeakerName.length()==0)displayFont=&VisualNovel::narratorFont;
|
|
|
|
std::u32string newText=util::WrapText(game,vn.activeText+(vn.activeText.length()>0?U" ":U"")+std::u32string(processedDialog.begin(),processedDialog.end()),game->GetScreenSize().x-24,*displayFont,{1,1});
|
|
|
|
if(VisualNovel::font.GetStringBounds(newText).size.y>48){//Hit the maximum of 3 lines.
|
|
|
|
if(!mustDisplay){
|
|
|
|
vn.commandIndex--;
|
|
|
|
}else{
|
|
|
|
vn.activeText=newText;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
vn.activeText=newText;
|
|
|
|
if(vn.commandIndex<vn.commands.size()){
|
|
|
|
CommandType::CommandType nextCommandType=vn.commands[vn.commandIndex]->GetType();
|
|
|
|
if(nextCommandType==CommandType::DIALOG){ //Only add to dialog when the next command type is a dialog as well.
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DialogCommand::DialogCommand(std::string dialog)
|
|
|
|
:dialog(dialog){}
|
|
|
|
CommandType::CommandType DialogCommand::GetType(){return CommandType::DIALOG;}
|
|
|
|
|
|
|
|
void PauseCommand::Execute(VisualNovel&vn){
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
PauseCommand::PauseCommand(){}
|
|
|
|
CommandType::CommandType PauseCommand::GetType(){return CommandType::PAUSE;}
|
|
|
|
|
|
|
|
void AudioPitchCommand::Execute(VisualNovel&vn){
|
|
|
|
vn.audioPitch=pitch;
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
AudioPitchCommand::AudioPitchCommand(std::string pitch)
|
|
|
|
:pitch(std::stof(pitch)){}
|
|
|
|
CommandType::CommandType AudioPitchCommand::GetType(){return CommandType::AUDIOPITCH;}
|
|
|
|
|
|
|
|
void BGMCommand::Execute(VisualNovel&vn){
|
|
|
|
Audio::PlayBGM(songName);
|
|
|
|
vn.ExecuteNextCommand();
|
|
|
|
}
|
|
|
|
BGMCommand::BGMCommand(std::string songName)
|
|
|
|
:songName(songName){}
|
|
|
|
CommandType::CommandType BGMCommand::GetType(){return CommandType::BGM;}
|