Up.
OD RAZU MÓWIE ŻE FUNKCJA MAIN
W REPO TO TAKI WIELKI POLIGON, docelowo będzie to ładniejsze ;>
Szczwany ja usunąłem poprzednie posty żeby nie było double znowu.
Dziś opowiem jak to działa.
Projekt jest rozwijany nadal, mam oczywiście szkołe ale i tak dosyć sprawnie idzie.
Kod silnika pisze jak na razie tylko ja, reszta czeka na skończenie go i wtedy bierzemy sie za gre.
To co doszło to:
Okienka z chmurkami, jescze nie dopicowane.

Dialogi oparte na drzewach n-arnych, wczytywane rekurencyjnie, co do głębokości to.. Nie mam pojęcia, pewnie dopókie sie nie wywali stos.


Dialogi były jedną z ciekawszych rzeczy z którymi miałem spory problem, ale to dobrze, bo przynajmniej czegoś sie nauczyłem :)
Struktura pliku z dialogami wygląda tak, gdzie musi być sekwencja
Reply( rozpoczyna rozmowe )
Potem player->reply->player->reply, i tak dalej.
Ogólnie "format" wygląda dosyć brzydko przy bardziej rozbudowanym dialogu, ale jest łatwy w edycji, strukture można zaobserwować na dialogach nr 2 i 3 bo są krótsze.
condition
to warunek który będzie musiał być spełniony aby odpowiedź była widoczna, i będe robił coś w stylu VM która będzie to obsługiwać.
action
natomiast to akcja którą wykonuje npc i enum od akcji wygląda tak
Kopiuj
<?xml version="1.0" encoding="UTF-8"?>
<dialogs>
<npc id="1">
<reply text="Witaj!">
<player text="Witaj magu!">
<reply text="Chcesz questa?">
<player text="Tak">
<reply text="Na moczarach są złote kalesony, przynieś je!">
<player text="Podejme się!" condition="level > 10">
<reply text="A więc idź, czekam!" action="3" questname="moczary0">
</reply>
</player>
<player text="Nie interesuje mnie to zadanie">
<reply text="Cóż, szkoda" >
</reply>
</player>
</reply>
</player>
<player text="Nie">
<reply text="Twoja strata :P">
</reply>
</player>
<player text="Co dostane w zamian?">
<reply text="Nagrode, bardzo chojną">
</reply>
</player>
</reply>
</player>
<player text="Dawaj itemy" >
<reply text="Czy to napad?">
<player text="Tak" condition="strength > 50">
<reply text="Okej koksie nie bij, masz" action="1" params="103,22,45,125,1024">
</reply>
</player>
<player text="Nie">
<reply text="Uff, to dobrze">
</reply>
</player>
<player text="Być może">
<reply text="Żadam dokładnej odpowiedzi">
<player text="Tak">
<reply text="Więc walcz!">
</reply>
</player>
<player text="Nie">
<reply text="Uff, to dobrze">
<player text="Nom">
<reply text="Hiho">
<player text="Co?">
<reply text="Nic">
<player text="Może jednak coś?">
<reply text="Nie, nic">
<player text="Na pewno?">
<reply text="No, na pewno">
</reply>
</player>
<player text="Daj piniążek">
<reply text="Nie mam :(">
<player text="Masz masz">
<reply text="Jakbym miał, i tak bym nie dał">
</reply>
</player>
<player text="No rozumiem, bida w krainie">
<reply text="Niestety">
<player text="No daj dolara">
<reply text="No nie mam, serio">
<player text="maszmaszmaszmasz">
<reply text="Dobra żebraku, masz 10 golda i ić sobie">
<player text="Dziękuje Ci">
<reply text="Idź już">
</reply>
</player>
<player text="A dej jescze 10 golda">
<reply text="O ty chamie, walcz!">
</reply>
</player>
<player text="Daj jescze plix">
<reply text="Ile?">
<player text="1 zet">
<reply text="Tyle Ci moge dać">
</reply>
</player>
<player text="3 zeta">
<reply text="Okej, ale nie pokazuj sie tu przez tydzień!">
</reply>
</player>
<player text="10 zeta">
<reply text="O ty chamie!">
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
<player text="Żegnaj">
<reply text="3m sie">
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
<player text="Test1">
<reply text="Test3">
</reply>
</player>
<player text="Test2">
<reply text="Test4">
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
<player text="Żegnaj">
<reply text="trzym sie">
</reply>
</player>
</reply>
</npc>
<npc id="2">
<reply text="Witaj w sklepie u Zbycha">
<player text="Witaj, co masz do sprzedania?">
<reply text="Spójrz">
</reply>
</player>
<player text="Co skupujesz?">
<reply text="Spójrz">
</reply>
</player>
<player text="Żegnaj!">
<reply text="Cześć">
</reply>
</player>
</reply>
</npc>
<npc id="3">
<reply text="Witaj :P">
<player text="Siema, co tam?">
<reply text="A dobrze, dobrze">
</reply>
</player>
<player text="Test">
<reply text="Git Test">
</reply>
</player>
<player text="Testowy tekst">
<reply text="Testowa odpowiedź ;)">
</reply>
</player>
</reply>
</npc>
</dialogs>
Kopiuj
enum ATYPE
{
NPC_ONLY_TALK, NPC_GIVE_ITEM, NPC_GET_ITEM, NPC_GIVE_QUEST,
NPC_VALID_QUEST, NPC_FIGHT, NPC_TRADE, NPC_RESERVED_FIELD_0, NPC_RESERVED_FIELD_1,
PLAYER_RESPONSE, ACTION_NOT_VALID=64
};
params
to parametry liczbowe parsowane i ładowane do std::vector<int>
, czyli np itemy które npc da graczowi.
Struktury z danymi pojedyńczego węzła zaprojektowałem tak, każdy NPC posiada n-arne drzewo struktur Action
(dla każdego węzła przypada
jedna struktura, która będzie potem przetwarzana na czymś ala-VM i generowane będą odpowiednie sygnały np OPEN_SHOP, lub VM sama w sobie będzie miała te wszystkie okna i ona będzie je tworzyć.)
Wiem że nie jest to super-wydajne rozwiąnie, ale nie wiem w sumie czy przy dzisiejszej mocy i ilości pamięci komputerów faktycznie takie coś jest problemem, wydaje mi sie - że nie, nie ukrywam że dynamiczne zarządzanie drzewem byłoby trudniejsze do zrobienia, ale nic, czekam na wasze opinie, jeśli to jest fe - poprawie to :)
Kopiuj
struct HelpInfo
{
HelpInfo()
{}
HelpInfo(std::string _cond)
: helpstring(_cond)
{}
HelpInfo(const HelpInfo& r)
{
this->additdata = r.additdata;
this->helpstring = r.helpstring;
}
HelpInfo& operator=(const HelpInfo& rhs)
{
this->additdata = rhs.additdata;
this->helpstring = rhs.helpstring;
return *this;
}
std::vector<int> additdata;
std::string helpstring;
};
struct Action
{
Action()
: type_action(NPC_ONLY_TALK)
, ad_info(){}
Action(std::wstring t, ATYPE at, HelpInfo hi){
text = t;
type_action = at;
ad_info = hi;
}
Action(const Action& r){
this->type_action = r.type_action;
this->ad_info = r.ad_info;
this->text = r.text;
}
void clean(){
type_action = NPC_ONLY_TALK;
ad_info.helpstring.clear();
ad_info.additdata.clear();
}
ATYPE type_action;
HelpInfo ad_info;
std::wstring text;
};
Pomyślałem również że może ktoś/ja kiedyś będe chciał skorzystać drugi raz z niektórych pomysłów, więc wczytywanie i parsowanie danych, oraz formatowanie danych w chmurce, uczyniłem strategiami, które wyglądają tak.
Kopiuj
class IDataFormat
{
public:
IDataFormat(){};
virtual void format(const ItemData, const sf::Vector2f&,unsigned int&, sf::Font&, std::vector<sf::Text>&) = 0;
virtual ~IDataFormat(){};
};
class IDataRead
{
public:
IDataRead(){};
virtual void read(TiXmlElement*) = 0;
virtual Action get() = 0;
virtual ~IDataRead(){};
};
Przykładowo obiekt dziedziczący z IDataFormat()
, formatuje dane w chmurce w taki sposób.
Gdzie stricte CAŁE dane nt ułożenia, koloru itp, są przetwarzane na tej funkcji.
Wywoływana ona jest dosyć czytelnie(chyba :p) w taki sposób
Kopiuj
void EquipmentWindow::controlCloud(sf::Vector2i vct)
{
unsigned int gid = getGID(vct);
if(gid)
{
descriptions.clear();
formatter.format(_imgr.getData(gid), cloud.getPosition(),cloud_border,font,descriptions);
hoover = true;
cloud.setPosition(sf::Vector2f(vct.x+15, vct.y));
return;
}
hoover = false;
}
Kopiuj
void EquipFormat::format(const ItemData itm, const sf::Vector2f& cloud_pos,unsigned int &border,sf::Font& font,std::vector<sf::Text>& desc)
{
std::basic_string<sf::Uint32> utf32;
sf::Color col;
sf::Utf8::toUtf32(itm.name.begin(), itm.name.end(), std::back_inserter(utf32));
sf::Text some_name(utf32, font, 15);
switch(itm.quality)
{
case 0: col = sf::Color::Blue; break;
case 1: col = sf::Color::Black; break;
case 2: col = sf::Color::Yellow; break;
case 3: col = sf::Color::Red; break;
}
some_name.setColor(col);
some_name.setPosition(cloud_pos.x, cloud_pos.y);
desc.push_back(some_name);
switch(itm.type)
{
case WEAPON:
utf32.clear();
utf32 = to32UTF<std::string>("Atak:");
utf32 += to32UTF<std::string>(std::to_string(itm.attack));
some_name.setString(utf32);
some_name.setCharacterSize(13);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+16);
some_name.setColor(sf::Color::Red);
desc.push_back(some_name);
utf32.clear();
utf32 = to32UTF<std::string>("Obrona:");
utf32 += to32UTF<std::string>(std::to_string(itm.defence));
some_name.setString(utf32);
some_name.setColor(sf::Color::Green);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+(14*2));
desc.push_back(some_name);
utf32.clear();
utf32 = to32UTF<std::string>("Szybkość:");
utf32 += to32UTF<std::string>(std::to_string((int)itm.speed));
utf32 += to32UTF<std::string>("%");
some_name.setString(utf32);
some_name.setColor(sf::Color::Cyan);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+(14*3));
desc.push_back(some_name);
break;
case ARMOR:
utf32.clear();
utf32 = to32UTF<std::string>("Obrona:");
utf32 += to32UTF<std::string>(std::to_string(itm.defence));
some_name.setString(utf32);
some_name.setCharacterSize(13);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+16);
some_name.setColor(sf::Color::Green);
desc.push_back(some_name);
break;
case RECOVERY:
utf32.clear();
utf32 = to32UTF<std::string>("Siła odnawiania:");
utf32 += to32UTF<std::string>(std::to_string(itm.power));
utf32 += to32UTF<std::string>("%");
some_name.setString(utf32);
some_name.setColor(sf::Color::Yellow);
some_name.setCharacterSize(10);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+16);
desc.push_back(some_name);
break;
default:
break;
}
utf32.clear();
utf32 = to32UTF<std::wstring>(itm.description);
some_name.setString(utf32);
some_name.setCharacterSize(13);
some_name.setColor(sf::Color::Yellow);
some_name.setPosition(cloud_pos.x, cloud_pos.y+(16*4));
desc.push_back(some_name);
}
Natomiast wczytywanie/parsowanie węzłów drzewa rozwiązane zostało tak.
Oto rekurencyjna funkcja wczytująca drzewo dialogów, dane przetwarzane są w funkcji read
, i pobierane funkcją get
.
Kopiuj
void NpcManager::loadTree(TiXmlElement* npc_el, NPC &npc, tree<Action>::sibling_iterator go_deeper, bool first)
{
TiXmlElement *reply = npc_el;
tree<Action>::iterator root;
tree<Action>::sibling_iterator sit;
std::string txt = reply->Attribute("text");
Action _tmp(wide_string<std::wstring>(txt), NPC_ONLY_TALK, HelpInfo());
if(first)
root = npc.dialog.insert(npc.dialog.begin(), _tmp);
if(reply->FirstChildElement("player") != NULL)
{
TiXmlElement *player = reply->FirstChildElement("player");
if(first)
sit = root;
else
sit = go_deeper;
while(player)
{
reader.read(player);
auto p_reply = npc.dialog.append_child(sit,reader.get());
if(player->FirstChildElement("reply") != NULL)
{
TiXmlElement* npc_quote = player->FirstChildElement("reply");
reader.read(npc_quote);
npc.dialog.append_child(p_reply,reader.get());
loadTree(player->FirstChildElement("reply"), npc, p_reply, 0);
}
player = player->NextSiblingElement("player");
}
}
else return;
}
Parsowanie rozwiązałem tak, current_processed
to struktura Action
w polach klasy DataRead
, która pobierana jest funkcją get()
.
Generalnie zrobiłem tak, żeby funkcje które nie odpowiadają stricte za parsowanie, nie były tak "opasłe", co prawda mógłbym uczynić to metodą
klasy NpcManager
gdzie wczytuje dane, ale przy modyfikacjach I TAK musiałbym tą klase modyfikować, a jak pisałem, nie chce tego, co najwyżej rozszerzyć strukture o odpowiednie pola.
Kopiuj
void DataRead::read(TiXmlElement* ent)
{
current_processed.clean();
current_processed.text = wide_string<std::wstring>(ent->Attribute("text"));
std::string parsed_node_type = ent->Value();
if(parsed_node_type == "reply")
{
if(ent->Attribute("action") != NULL)
{
current_processed.type_action = (ATYPE)(atoi(ent->Attribute("action")));
std::string params;
if(ent->Attribute("params") != NULL)
params = ent->Attribute("params");
switch(current_processed.type_action)
{
case NPC_GIVE_ITEM:
parseParams(params,current_processed.ad_info.additdata,',');
break;
case NPC_GET_ITEM:
parseParams(params,current_processed.ad_info.additdata,',');
break;
case NPC_GIVE_QUEST:
if(ent->Attribute("questname") != NULL)
current_processed.ad_info.helpstring = ent->Attribute("questname");
break;
case NPC_TRADE:
parseParams(params,current_processed.ad_info.additdata,',');
break;
}
}
}
if(parsed_node_type == "player")
{
ATYPE type;
if(ent->Attribute("action") == NULL)
type = PLAYER_RESPONSE;
else if(ent->Attribute("action") != NULL)
{
type = (ATYPE)atoi(ent->Attribute("action"));
if(type < PLAYER_RESPONSE)
type = ACTION_NOT_VALID;
}
current_processed.type_action = type;
if(ent->Attribute("condition") != NULL)
current_processed.ad_info.helpstring = ent->Attribute("condition");
}
}
pull request
, żeby jako tako można było code-review robić, chociaż po tej najlżejszej linii oproupull
ów.