Design: Klassen nach Idee oder nach Implementierung wählen
-
Ich hatte mir jetzt über Nacht noch ein paar Gedanken gemacht, allerdings habe ich die Posts von lolz und mathiks da noch nicht gelesen gehabt.
@lolz: wie würde so etwas denn technisch aussehen? Ich wollte das so machen, dass ich quasi einen Talenttree (den ich vermutlich als std::map speichere) übergebe und dann in den entsprechenden Zaubern überprüfe ob das Talent geskillt ist oder nicht.
@mathiks: ich werde mir diese pattern dann mal bei wikipedia suchen, vielen Dank.
Ansonsten, also ohne die beiden Posts hätte ich das jetzt folgendermaßen gemacht (die Talente eines Magiers findet man z.B unter http://www.wow-europe.com/de/info/basics/talents/mage/talents.html wenn sich jemand ein Bild machen wollen sollte)
Ich würde nun eine abstrakte Basisklasse SchadensZauber machen. Von dieser Basisklasse würde ich weitere abstrakte Klassen für jede Magieschule (jetzt am Anfang nur Feuer, Eis und Arkan) ableiten, und von diesen Magieschulen dann immer die entsprechenden Zauber (Feuerball von Feuer, Frostblitz von Eis, Feuerschlag von Feuer usw.) Wenn ich nun einen Talenttree habe (siehe Link oben), vermutlich als std::map gespeichert, dann übergebe ich den an jede einzelne Klasse (dazu muss ich ja keine Instanz der Klasse erstellen). Nun schaut z.B die Klasse FeuerZauber nach allen Talenten, die alle Feuerzauber betreffen (z.B. Feuermacht, siehe Link) und speichert dann quasi den Schadensmodifikator (Schaden wird um 10% erhöht--> speichere 1,1) Der Feuerball sieht sich dann nur die Talente an, die ihn allein betreffen.
Am Ende wird dann für jeden Zauber die Berechnung aufgerufen, und diese rufen dann eben die gespeicherten Modifikatoren in ihren Basisklassen ab (Feuerball fragt nach den Modifikatoren von FeuerZauber, und FeuerZauber wiederum nach denen von SchadensZauber) und berechnet es entsprechend.
Achja, bez. des Damage over Time vs Direct Damage: Ich gebe einfach jedem Zauber alle 4 Attribute (4, weil: Castzeit, Schadensdauer, direkter Schaden und Schaden über Zeit) und setze eben einfach entsprechend den Schaden über Zeit oder den direkten Schaden auf 0, je nachdem was zutrifft. Ich denke so kann ich vielleicht konsistenter arbeiten und habe auch keine Probleme mehr mit Zaubern, die beides haben.
Klingt das nach einem vernünftigen Design? Oder eher nach absolutem Blödsinn?
PS: die Pattern von mathiks sehe ich mir aber natürlich trotzdem noch an.
-
@Shinja bei der map muss die Feurzauberklasse halt alle potentiellen Einträge kennen. Aber deine Idee ist schon sehr nahe an der von mir vorgeschlagenen Modifikatoren.
Die Klassenunterteilung kannst du im Grunde so lassen (dot und dd zusammen find ich gut aus den von dir genannten gründen). Das System ist so zwar sehr statisch, aber für dein Programm ist das ja egal.Zu den Modifikatoren schreib ich nacher mal was, hab gerade wenig Zeit.
-
Hallo lolz,
vielen Dank dann Mal, ich werde nachher (muss auch bald weg) anfangen, das so zu implementieren, das mit den Modifikatoren übernehm ich dann wenn du es geschrieben hast.
Dass es statisch ist ist in diesem Fall ja kein Problem, so oft ändert blizz die Zauber oder Talente ja net, und wenn net find ich es ja schnell, weil eben jedes Talent nur an einer oder zwei Stellen wirken muss.
Das mit den Modifikatoren interessiert mich mal, mir gefällt nämlich aktuell noch etwas nicht so 100%. Ich habe zwar eine Lösung dafür, aber ob die so toll ist weisz ich nicht. Ich implementier es mal soweit und wenn erlaubt stelle ich dann hier den Quelltext online zur Begutachtung, falls einer Zeit hat.
-
Ich hab jetzt mal auf die Schnelle folgendes geschrieben, schaus dir mal an darunter erklärt ich paar Sachen dazu:
struct Modifier { //in abgeleiteten Klassen dann ändern was man ändern will virtual void modify( Spell& spell ) = 0; }; class Spell { friend class Modifier; int directDamage; int damageOverTimer; int duration; //vom dot int manaCost; time_t castTime; //0 = instant, sonst ct in ms std::vector< Modifier > modifiers; public: Spell( int dd, int dot, int dur, int mana, time_t ct ) : directDamage( dd ), damageOverTimer( dot ), duration( dur ), manaCost( mana ), castTime( ct ) { } void pushModifier( const Modifier& mod ) { modifiers.push_back( mod ); } void popModifier() { modifiers.pop_back(); } //mal vereinfacht void cast( /* Target& t */) { for( std::vector< Modifier >::iterator it = modifiers.begin(), end = modifiers.end(); it != end; ++it ) { it->modify( *this ); } //bsp: //t.doDamage( directDamage ); //... } };Das ist meine Idee mal auf das direkteste runtergeschraubt (für deinen Zweck denk ich ist das ok so).
Je nachdem reicht dir das so schon nicht mehr, weil die Modifikationen sich eventuell gegenseitig beachten müssen (z.B. wenn man zwei mal +10% hat sollen die doch auf die ursprünglichen 100% 20% drauf tun und nicht der eine auf 100% und der andere auf 110%). Wenn das der Fall ist müsste man das System doch deutlich aufmotzen und dann würd ich dir doch zur std::map raten, dann bist du im Endeffekt schneller.Auch hier hast du noch das Problem, dass das ganze nacher statisch ist, aber so oft ändert sich ja nichts und dann musst du nur ne Klasse für das Talent hinzufügen.
Die Handhabung selbst ist weitgehend selbsterklärend (denk ich).
Da du aber alles direkt in C++ schreibst hast du natürlich auch hier eine Stelle wo eine Zauber->Talente Zuordnung feststeht. Aber wie du selbst gesagt hast, das ändert sich ja nicht ständig.
Edit:
Noch nen kleines Beispiel (musst allerdings einige Zeilen des obigen Codes ändern):
struct EnhanceDD : Modifier { EnhanceDD( float percent ) : percent( std::min< float >( percent, 100.0 ) ) { } void modify( Spell& spell ) { float total = spell.directDamage; total *= ( 1.0f + percent ); spell.directDamage = int( total ); } float percent; }; struct AddDot : Modifier { AddDot( int dmg, int dur ) : dmg( dmg ), dur( dur ) { } void modify( Spell& spell ) { //Hier bräucht man auch eher nen dot-stack damit jeder dot seine eigenen //ticks hat spell.damageOverTimer += dmg; spell.duration += dur; } int dmg, dur; }; int main() { //Zauber mit 100Schaden, 50Manakosten und 0.5sec ct Spell s( 100, 0, 0, 50, 500 ); //Verstärkt DD um 10% EnhanceDD es( 0.1 ); //Fügt einen dot zum Zauber dazu //mit 250Schaden über 30Sekunden AddDot ad( 250, 30 ); s.pushModifier( &es ); s.pushModifier( &ad ); s.cast(); std::cout << s.directDamage; std::cout << "\t" << s.damageOverTimer << "\t" << s.duration; }
-
Ok, vielen Dank, das sieht interessant aus, ich werde mir das mal genauer ansehen. Was ich beim ersten Überblick nicht verstanen habe ist, wer dann am Ende weisz, wo ich welchen Modifier hinpushen muss. Das muss ich mir doch immer noch abspeichern, oder nicht?
Vielen Dank für die Arbeit, ich hoff ich weisz es zu nutzen, hehe (naja, wenn nicht frag ich halt nochmal)
PS: Zu der 2x 10%: so gut wie alle Modifikationen durch Talente wirken multiplikativ, das macht es also recht einfach. Wenn ich also aufgrund von castzeit für einen spell nur 90% bekomme, durch ein Talent aber 6% mehr, durch ein anderes nochmal 5% mehr, dann sollte die Rechnung, wenn ich es richtig in Erunnerung habe, 0,9*1,06*1,05 sein. Das macht das ganze relativ einfach, habe ich gefunden, in der Form (ganz grob):
class BasisZauber{ int ddmod; //alle anderen benötigten Modifikatoren, für Reichweite o.ä. protected: void setmod(const talenttree& talents){ //Nach den Talenten die eine Ausiwkrung haben einzeln suchen und entsprechende Modifikation vornehmen } int getddmod() const{ return ddmod; } //alle anderen geter für die verschiedenen mods public: //als pure virtual die Funktionen die ich zur berechnung und ausgabe brauche }; class FeuerZauber:private BasisZauber{ int ddmod; //alle anderen benötigten Modifikatoren, für Reichweite o.ä. protected: void setmod(const talenttree& talents){ BasisZauber::setmod(talents); //Nach den Talenten die eine Ausiwkrung haben einzeln suchen und entsprechende Modifikation vornehmen } int getddmod const { return ddmod*BasisZauber::getddmod(); } }; class FeuerBall:private FeuerZauber{ //alle Attribute des Feuerballs int ddmod; //alle anderen benötigten Modifikatoren, für Reichweite o.ä. void setmod(const talenttree& talents){ FeuerZauber::setmod(talents); //Nach den Talenten die eine Ausiwkrung haben einzeln suchen und entsprechende Modifikation vornehmen } int getddmod() const{ return ddmod*FeuerZauber::getmod(); } //die getter für die anderen mods public: Feuerball(const talenttree& talents){ setmod(talents); } int getdamage() const{ return basedamage*ddmods; //basedamage eines der private Attribute } //alle anderen getter und weitere Funtkionen };So, das war meine bisherige Idee. talenttree ist halt dann die variante die ich zum Speichern der Talente wähle, das kann ne einfache std::mapstd::string,int sein (string für den Talentnamen, int für die anzahl der talentpunkte)
Ich hatte mir dann auch überlegt, dem talenttree noch zusätzlich nen static int für eine ID zu geben, und die Klassen würden dann immer die ID des zuletzt bekommenen talenttrees speichern. Wenn sie sich nicht geändert hat müssen sie nichts mehr berechnen. (ansonsten würde bei diesem Design ja bei jedem einzelnen Feuerzauber nochmal die Berechnung der Klasse FeuerZauber durchgeführt werden, das wäre ja sinnlos)
Wie klingt das? Bin ich mit deiner methode (@lolz) schneller?
EDIT: das war vor dem Abschicken deines 2. Posts, sry, den hatte ich da noch nicht gesehen.
Warum ich die Zauber hardgecodet habe, also für jeden eine eigene Klasse, war, dass die Damageberechnung unter Umständen etwas speziell sein kann für bestimmte Zauber. Aber wie ich jetzt sehe kriege ich das mit den Modifiern auch hin, und das ganze wäre weitaus dynamischer.
-
Das wichtigste habt ihr alle vergessen:
if(class == WARLOCK) spellImba = true;
-
Was ich beim ersten Überblick nicht verstanen habe ist, wer dann am Ende weisz, wo ich welchen Modifier hinpushen muss. Das muss ich mir doch immer noch abspeichern, oder nicht?
Das musst du (hab ich oben auch geschrieben, hast du beim übefliegen wohl übersehen).
PS: Zu der 2x 10%: so gut wie alle Modifikationen durch Talente wirken multiplikativ, das macht es also recht einfach. Wenn ich also aufgrund von castzeit für einen spell nur 90% bekomme, durch ein Talent aber 6% mehr, durch ein anderes nochmal 5% mehr, dann sollte die Rechnung, wenn ich es richtig in Erunnerung habe, 0,9*1,06*1,05 sein. Das macht das ganze relativ einfach, habe ich gefunden, in der Form (ganz grob):
Dann wäre es natürlich kein Problem, das so zu machen.
(ansonsten würde bei diesem Design ja bei jedem einzelnen Feuerzauber nochmal die Berechnung der Klasse FeuerZauber durchgeführt werden, das wäre ja sinnlos)
Das wäre bei meinem auch der Fall, du könntest zwischen passiven und aktiven unterscheiden und die Passiven nur explizit aufrufen (mit ner eigenen Methode) und die aktiven bei jedem casten. Oder du berechnest immer beim hinzufügen/entfernen eines Talents und nicht beim zaubern (wäre wohl besser).
Du könntest die Modifier natürlich als Implementierung der Details (eines Zaubers) verwenden, aber ändert nix daran, dass du ne C++-Klasse schreiben musst. Von daher würd ich den direkten Weg gehen und für spezielle Zauber ne Klasse zu schreiben.
-
Ok, vielen Dank lolz, hast mir sehr geholfen
@kenner: papperlapp, mage > all (wobei es bei wls etwas sehr schwer wird, lol)
-
Shinja schrieb:
PS: Zu der 2x 10%: so gut wie alle Modifikationen durch Talente wirken multiplikativ, das macht es also recht einfach. Wenn ich also aufgrund von castzeit für einen spell nur 90% bekomme, durch ein Talent aber 6% mehr, durch ein anderes nochmal 5% mehr, dann sollte die Rechnung, wenn ich es richtig in Erunnerung habe, 0,9*1,06*1,05 sein.
Woher weißt du die Reihenfolge?

-
Lügner schrieb:
Shinja schrieb:
PS: Zu der 2x 10%: so gut wie alle Modifikationen durch Talente wirken multiplikativ, das macht es also recht einfach. Wenn ich also aufgrund von castzeit für einen spell nur 90% bekomme, durch ein Talent aber 6% mehr, durch ein anderes nochmal 5% mehr, dann sollte die Rechnung, wenn ich es richtig in Erunnerung habe, 0,9*1,06*1,05 sein.
Woher weißt du die Reihenfolge?

?