blob: 503da97d4b401876ad36ebd2f69be4fb9560cd52 [file] [log] [blame]
// MorgenGrauen MUDlib
//
// player/skills.c -- Spielerskills
//
// $Id: skills.c 8809 2014-05-08 19:52:48Z Zesstra $
//
// 2003-01-20: Nun Zooks Baustelle
//
#pragma strong_types
#pragma save_types
#pragma range_check
#pragma no_clone
#pragma pedantic
inherit "/std/living/skills";
#include <combat.h>
#include <new_skills.h>
#include <properties.h>
#include <break_string.h>
#include <wizlevels.h>
#define NEED_PROTOTYPES
#include <player/base.h>
#include <player/gmcp.h>
#undef NEED_PROTOTYPES
// Dieses Mapping speichert die deaktivierten Skills der einzelnen Gilden
// Diese werden in den Gilden ueber P_GUILD_DEACTIVATED_SKILL gesetzt.
nosave mapping deactivated_skills = ([]);
// Flag fuer den Kompatibilitaetsmodus des Kampfs (Emulation von
// 2s-Alarmzeit). Wenn != 0 speichert das Flag gleichzeitig die Zeit des
// letzten Heartbeats. Auf diese Zeit wird der Startpunkt eines Spellfatigues
// ggf. zurueckdatiert. (max. eine Sekunde)
int spell_fatigue_compat_mode;
// Ein create() fuer das Mapping
protected void create()
{
mapping act;
::create();
// Wir holen die Gilden aus dem Gildenmaster
foreach(string guild:
(string *)call_other(GUILDMASTER,"QueryProp",P_VALID_GUILDS))
{
if(catch(act=call_other("/gilden/"+guild,"QueryProp",
P_GUILD_DEACTIVATE_SKILLS); publish ))
log_file("WEAPON_SKILLS", sprintf ("%s: Gilde nicht ladbar: "
+"TP: %O, TI: %O, PO: %O, Gilde: %s\n", dtime(time()),
this_player(), this_interactive(), previous_object(), guild));
else if (act) // wenn act, ins Mapping aufnehmen.
deactivated_skills+=([guild:act]);
}
// keine echte Prop mehr, Save-Modus unnoetig.
Set(P_NEXT_SPELL_TIME,SAVE,F_MODE_AD);
Set(P_SKILLSVERSION, SAVE|SECURED, F_MODE_AS);
}
// Das Mapping kann man auch abfragen
public mapping GetDeactivatedSkills()
{
return copy(deactivated_skills);
}
// Funktion, die sagt ob ein ANY-Skill deaktiviert ist.
public int is_deactivated_skill(string sname,string guild)
{
if (deactivated_skills[guild])
return deactivated_skills[guild][sname];
return 0;
}
// Funktion fuer die Waffenskills
// traegt die allg. Waffenskills ein. Wird ggf. von FixSkills() gerufen.
// (Das Eintragen bedeutet nicht, dass die aktiv sind! Aber bei Gildenwechsel
// werden sie nicht eingetragen).
private void set_weapon_skills() {
if (QuerySkillAbility(FIGHT(WT_SWORD))<=0)
ModifySkill(FIGHT(WT_SWORD),([SI_SKILLABILITY:0]),150,"ANY");
if (QuerySkillAbility(FIGHT(WT_AXE))<=0)
ModifySkill(FIGHT(WT_AXE),([SI_SKILLABILITY:0]),150,"ANY");
if (QuerySkillAbility(FIGHT(WT_SPEAR))<=0)
ModifySkill(FIGHT(WT_SPEAR),([SI_SKILLABILITY:0]),150,"ANY");
if (QuerySkillAbility(FIGHT(WT_WHIP))<=0)
ModifySkill(FIGHT(WT_WHIP),([SI_SKILLABILITY:0]),150,"ANY");
if (QuerySkillAbility(FIGHT(WT_KNIFE))<=0)
ModifySkill(FIGHT(WT_KNIFE),([SI_SKILLABILITY:0]),150,"ANY");
if (QuerySkillAbility(FIGHT(WT_CLUB))<=0)
ModifySkill(FIGHT(WT_CLUB),([SI_SKILLABILITY:0]),150,"ANY");
if (QuerySkillAbility(FIGHT(WT_STAFF))<=0)
ModifySkill(FIGHT(WT_STAFF),([SI_SKILLABILITY:0]),150,"ANY");
}
// initialisiert die Skills fuer Spieler (momentan: allg. Waffenskills setzen
// und P_SKILLS_VERSION)
protected void InitSkills() {
mapping ski;
// schonmal initialisiert?
if (mappingp(ski=Query(P_NEWSKILLS, F_VALUE)) && sizeof(ski))
return;
// allg. Waffenskills aktivieren
set_weapon_skills();
// Version setzen
SetProp(P_SKILLSVERSION, CURRENT_SKILL_VERSION);
Set(P_SKILLSVERSION, SAVE|SECURED, F_MODE_AS);
}
// Updated Skills aus Version 0 und 1 heraus.
private void FixSkillV1(string skillname, mixed sinfo) {
// alte Skills auf mappings normieren
if (intp(sinfo)) {
sinfo = ([SI_SKILLABILITY: sinfo ]);
}
// Eine Reihe von Daten werden geloescht, da die Daten aus der
// Gilde/Spellbook frisch kommen sollten und sie nicht spieler-individuell
// sind: SI_CLOSURE (wird onthefly korrekt neu erzeugt), SI_SKILLARG,
// SI_NUMBER_ENEMIES, SI_NUMBER_FRIENDS, SI_DISTANCE, SI_WIDTH, SI_DEPTH,
// SI_TESTFLAG, SI_ENEMY, SI_FRIEND.
// Ausserdem sind alle SP_* im toplevel falsch, die muessen a) in SI_SPELL
// und sollten b) nicht im Spieler gespeichert werden, sondern von
// Gilden/Spellbook jeweils frisch kommen.
// all dieses Zeug landete in alten Spielern im Savefile.
if (mappingp(sinfo))
sinfo -= ([SI_CLOSURE, SI_SKILLARG, SI_NUMBER_ENEMIES, SI_NUMBER_FRIENDS,
SI_DISTANCE, SI_WIDTH, SI_DEPTH, SI_TESTFLAG, SI_ENEMY,
SI_FRIEND, SP_NAME, SP_SHOW_DAMAGE, SP_REDUCE_ARMOUR,
SP_PHYSICAL_ATTACK, SP_RECURSIVE, SP_NO_ENEMY,
SP_NO_ACTIVE_DEFENSE, SP_GLOBAL_ATTACK ]);
else
{
tell_object(this_object(),sprintf(
"\n**** ACHTUNG - FEHLER ***\n"
"Deine Skills enthalten einen defekten Skill %O:\n"
"Bitte lass dies von einem Erzmagier ueberpruefen.\n",
skillname));
}
}
// Updatet und repariert ggf. Skillmappings in Spielern
protected void FixSkills() {
// nur bei genug rechenzeit loslegen
if (get_eval_cost() < 750000) {
call_out(#'FixSkills, 1);
return;
}
// wenn gar keine Skills da (?): InitSkills() rufen.
mapping allskills = Query(P_NEWSKILLS, F_VALUE);
if (!mappingp(allskills) || !sizeof(allskills)) {
InitSkills();
return;
}
// Die Fallthroughs in diesem switch sind voll Absicht!
switch(QueryProp(P_SKILLSVERSION)) {
// bei Version 0 und 1 das gleiche tun
case 0: // von 0 auf 1
case 1: // von 1 auf 2
foreach(string gilde, mapping skills: allskills) {
if (!stringp(gilde)) {
// sollte nicht vorkommen - tat aber... *seufz*
m_delete(skills, gilde);
continue;
}
walk_mapping(skills, #'FixSkillV1);
}
// allg. Waffenskills aktivieren, einige alte Spieler haben die noch
// nicht.
set_weapon_skills();
// Speicherflag fuer die Versionsprop muss noch gesetzt werden.
Set(P_SKILLSVERSION, SAVE|SECURED, F_MODE_AS);
// Version ist jetzt 2.
SetProp(P_SKILLSVERSION, 2);
// Fall-through
case 2:
// gibt es noch nicht, nichts machen.
//SetProp(P_SKILLSVERSION, 3);
// Fall-through, ausser es sind zuwenig Ticks da!
if (get_eval_cost() < 750000)
break;
}
// Falls noch nicht auf der aktuellen Version angekommen, neuer callout
if (QueryProp(P_SKILLSVERSION) < CURRENT_SKILL_VERSION)
call_out(#'FixSkills, 2);
}
protected void updates_after_restore(int newflag) {
//Allgemeine Waffenskills aktivieren, wenn noetig
// Wird nun von InitSkills bzw. FixSkills uebernommen, falls noetig.
if (newflag) {
InitSkills();
}
else if (QueryProp(P_SKILLSVERSION) < CURRENT_SKILL_VERSION) {
// Falls noetig, Skills fixen/updaten. *grummel*
FixSkills();
}
// Prop gibt es nicht mehr. SAVE-Status loeschen.
Set(P_GUILD_PREVENTS_RACESKILL,SAVE,F_MODE_AD);
}
// Standardisierte Nahkampf-Funktion fuer alle Nahkampf-Waffenarten
protected mapping ShortRangeSkill(object me, string sname, mapping sinfo)
{
int val, w;
object enemy;
if (!mappingp(sinfo) || !objectp(sinfo[P_WEAPON]))
return 0;
w = ([WT_KNIFE : 8,
WT_SWORD : 5,
WT_AXE : 4,
WT_SPEAR : 6,
WT_CLUB : 1,
WT_WHIP : 9,
WT_STAFF : 7])[sinfo[P_WEAPON]->QueryProp(P_WEAPON_TYPE)];
val = sinfo[SI_SKILLABILITY]*(sinfo[P_WEAPON]->QueryProp(P_WC)*
(w*QueryAttribute(A_DEX)+
(10-w)*QueryAttribute(A_STR))/700)
/MAX_ABILITY;
if (val > 85) {
log_file("WEAPON_SKILLS", sprintf("%s: Zu hoher Schaden von: "
+"TO: %O, TI: %O, PO: %O, val: %d, A_DEX: %d, A_STR: %d, "
+"P_WEAPON: %O, P_WC: %d\n", dtime(time()),
this_object(), this_interactive(),
previous_object(), val,
QueryAttribute(A_DEX),
QueryAttribute(A_STR), sinfo[P_WEAPON],
sinfo[P_WEAPON]->QueryProp(P_WC)));
val = 85;
}
/*
Der zusätzliche Schaden der allgemeinen Waffenskills berechnet
sich wie folgt:
sinfo[SI_SKILLABILITY)* (P_WC * ( X ) / 800) / MAX_ABILITY
Dabei beruecksichtigt X je nach Waffentyp in unterschiedlicher
Gewichtung die Werte fuer Geschicklichkeit und Staerke.
X ==
Messer : 8*A_DEX + 2*A_STR
Schwert : 5*A_DEX + 5*A_STR
Axt : 4*A_DEX + 6*A_STR
Speer : 6*A_DEX + 4*A_STR
Keule : 1*A_DEX + 9*A_STR
Peitsche : 9*A_DEX + 1*A_STR
*/
sinfo[SI_SKILLDAMAGE]+=val;
/* Lernen: Wird immer schwieriger, nur bei jedem 20. Schlag im Schnitt,
* und nur dann, wenn der Gegner auch XP gibt. */
if (random(MAX_ABILITY+1)>sinfo[SI_SKILLABILITY] && !random(10))
{
enemy=sinfo[SI_ENEMY];
if (objectp(enemy) && (enemy->QueryProp(P_XP)>0))
{
object ausbilder;
// log_file("humni/log_wurm","Haut: %s und zwar %s, mit xp %d\n",geteuid(this_object()),to_string(enemy),enemy->QueryProp(P_XP));
LearnSkill(sname, random(5), 150);
// Gibt es einen Ausbilder?
if (QueryProp(P_WEAPON_TEACHER) &&
ausbilder=find_player(QueryProp(P_WEAPON_TEACHER)))
{
// Ist der Ausbilder anwesend?
if (present(ausbilder,environment()))
{
// Ausbilder und Azubi muessen dieselbe Waffe haben.
//string wt_aus,wt_azu;
object waf_aus,waf_azu;
waf_azu=QueryProp(P_WEAPON);
waf_aus=call_other(ausbilder,"QueryProp",P_WEAPON);
//wt_azu=call_other(waf_azu,"QueryProp",P_WEAPON_TYPE);
//wt_aus=call_other(waf_aus,"QueryProp",P_WEAPON_TYPE);
//if (wt_azu==wt_aus)
if (objectp(waf_aus) && objectp(waf_azu) &&
(string)waf_aus->QueryProp(P_WEAPON_TYPE)
== (string)waf_azu->QueryProp(P_WEAPON_TYPE))
{
// Bonus von bis zu 5 Punkten
//log_file("humni/log_azubi",
// sprintf("Azubi %O und Ausbilder %O : Waffentypen %s und %s, gelernt\n",this_object(),
// ausbilder, wt_azu, wt_aus));
LearnSkill(sname,random(6),150);
}
}
}
}
}
/*
Die Schwierigkeit liegt bei 150, so dass
ein Lvl. 1 Spieler maximal 15% Skill
usw...
lernen kann. (Genaue Tabelle in /std/living/skills bei LimitAbility)
*/
return sinfo;
}
// Standardisierte Fernkampf-Funktion fuer alle Fernkampf-Waffenarten
// *** noch deaktiviert ***
protected mapping LongRangeSkill(object me, string sname, mapping sinfo, int dam)
{ int abil,val;
if (!mappingp(sinfo) || !dam || !objectp(sinfo[P_WEAPON]) ||
(sinfo[P_WEAPON]->QueryProp(P_SHOOTING_WC))<5)
return 0;
abil=sinfo[SI_SKILLABILITY]+sinfo[OFFSET(SI_SKILLABILITY)];
val=dam*abil/MAX_ABILITY;
val=val/2+random(val/2+1);
val=(val*QuerySkillAttribute(SA_DAMAGE))/100;
sinfo[SI_SKILLDAMAGE]+=val;
if (random(MAX_ABILITY+1)>sinfo[SI_SKILLABILITY] && random(50)==42)
LearnSkill(sname, 1, 150);
return sinfo;
}
// Die einzelnen Waffenskills rufen dann nur die Standard-Funktion auf.
protected mapping StdSkill_Fight_axe(object me, string sname, mapping sinfo)
{
return ShortRangeSkill(me, sname, sinfo);
}
protected mapping StdSkill_Fight_club(object me, string sname, mapping sinfo)
{
return ShortRangeSkill(me, sname, sinfo);
}
protected mapping StdSkill_Fight_knife(object me, string sname, mapping sinfo)
{
return ShortRangeSkill(me, sname, sinfo);
}
protected mapping StdSkill_Fight_spear(object me, string sname, mapping sinfo)
{
return ShortRangeSkill(me, sname, sinfo);
}
protected mapping StdSkill_Fight_sword(object me, string sname, mapping sinfo)
{
return ShortRangeSkill(me, sname, sinfo);
}
protected mapping StdSkill_Fight_whip(object me, string sname, mapping sinfo)
{
return ShortRangeSkill(me, sname, sinfo);
}
protected mapping StdSkill_Fight_staff(object me, string sname, mapping sinfo)
{
return ShortRangeSkill(me, sname, sinfo);
}
// Die Fernwaffenskills sind Munitionsabhaengig
// *** noch deaktiviert ***
protected mapping StdSkill_Shoot_arrow(object me, string sname, mapping sinfo)
{
return LongRangeSkill(me, sname, sinfo, 40);
}
protected mapping StdSkill_Shoot_bolt(object me, string sname, mapping sinfo)
{
return LongRangeSkill(me, sname, sinfo, 40);
}
protected mapping StdSkill_Shoot_dart(object me, string sname, mapping sinfo)
{
return LongRangeSkill(me, sname, sinfo, 20);
}
protected mapping StdSkill_Shoot_stone(object me, string sname, mapping sinfo)
{
return LongRangeSkill(me, sname, sinfo, 40);
}
protected mixed _query_localcmds() {
return ({ ({"spruchermuedung","enable_spell_fatigue_compat",0,0}) });
}
// *** Kompatibilitaetsmodus fuer Spellfatigues (Emulation 2s-Alarmzeit) ***
/** Speichert eine Spellfatigue von <duration> Sekunden fuer <key>.
* Ist hier nur fuer den Spellfatigue-Compat-mode.
* <key> darf 0 sein und bezeichnet das globale Spellfatigue.
* Rueckgabewert: Ablaufzeit der gesetzten Sperre
-1, wenn noch eine nicht-abgelaufene Sperre auf dem <key> lag.
0, wenn duration 0 ist.
*/
public varargs int SetSpellFatigue(int duration, string key) {
// 0 sollte nie eine Sperre bewirken, auch nicht im compat mode.
if (!duration) return 0;
if (spell_fatigue_compat_mode) {
// Spell-Fatigues auf HBs synchronisieren (2s-Alarmzeit-Emulation).
// Aufrunden auf ganzzahlige Vielfache von __HEART_BEAT_INTERVAL__
if (duration % __HEART_BEAT_INTERVAL__)
++duration;
// Startpunkt des Delay soll Beginn der aktuellen Kampfrunde (HB-Zyklus)
// sein, ggf. um max. eine Sekunde zurueckdatieren.
// (spell_fatigue_compat_mode hat die Zeit des letzten HB-Aufrufs)
// Falls durch irgendein Problem (z.B. sehr hohe Last), der letzte HB
// laenger als 1s zurueckliegt, funktioniert das natuerlich nicht, aber
// bei fatigue+=spell_fatigue_compat_mode kann der Spieler zuviel Zeit
// einsparen.
if (time() > spell_fatigue_compat_mode)
--duration; //1s zurueckdatieren
}
return ::SetSpellFatigue(duration, key);
}
/** Befehlsfunktion fuer Spieler um den Spellfatigue-Kompatibilitaetsmodus
* umzuschalten.
*/
public int enable_spell_fatigue_compat(string cmd) {
if (QueryProp(P_LAST_COMBAT_TIME) + 600 > time()) {
write(break_string(
"Im Kampf oder kurz nach einem Kampf kannst Du nicht zwischen "
"alter und neuer Spruchermuedung umschalten.\n"
"Momentan benutzt Du die "
+ (spell_fatigue_compat_mode ? "alte (ungenauere)" : "neue (normale)")
+ " Spruchermuedung.",78,0,BS_LEAVE_MY_LFS));
return 1;
}
if (cmd=="alt") {
spell_fatigue_compat_mode=time();
write(break_string(
"Alte Spruchermuedung wurde eingeschaltet. Alle Ermuedungspausen "
"zwischen Spruechen werden auf Vielfache von 2s aufgerundet und "
"beginnen in der Regel am Anfang Deiner Kampfrunde."));
return 1;
}
else if (cmd=="neu" || cmd=="normal") {
spell_fatigue_compat_mode=0;
write(break_string(
"Normale Spruchermuedung wurde eingeschaltet. Alle Ermuedungspausen "
"zwischen Spruechen werden sekundengenau berechnet."));
return 1;
}
notify_fail(break_string(
"Moechtest Du die alte oder die neue Spruchermuedung?\n"
"Momentan benutzt Du die "
+ (spell_fatigue_compat_mode ? "alte (ungenauere)" : "neue (normale)")
+ " Spruchermuedung.",78,0,BS_LEAVE_MY_LFS));
return 0;
}
/** Speichert die Zeit des letztes Heartbeats.
*/
protected void heart_beat() {
if (spell_fatigue_compat_mode)
spell_fatigue_compat_mode = time();
}
static int _set_guild_level(int num)
{ string gilde;
mapping levels;
if ( !(gilde=QueryProp(P_GUILD)) )
return 0;
if ( !mappingp(levels=Query(P_GUILD_LEVEL)) )
levels=([]);
levels[gilde]=num;
Set(P_GUILD_LEVEL,levels);
GMCP_Char( ([P_GUILD_LEVEL: num]) );
return num;
}
static int _query_guild_level()
{ string gilde;
mapping levels;
if ( !(gilde=QueryProp(P_GUILD)) )
return 0;
if ( !mappingp(levels=Query(P_GUILD_LEVEL)) )
return 0;
return levels[gilde];
}
static string _set_guild_title(string t)
{ string gilde;
mapping titles;
if ( !(gilde=QueryProp(P_GUILD)) )
return 0;
if ( !mappingp(titles=Query(P_GUILD_TITLE)) )
titles=([]);
titles[gilde]=t;
Set(P_GUILD_TITLE,titles);
GMCP_Char( ([P_GUILD_TITLE: t]) );
return t;
}
static string _query_guild_title()
{ string gilde,t;
object g;
mapping titles;
if ( !(gilde=QueryProp(P_GUILD)) )
return 0;
if ( !mappingp(titles=Query(P_GUILD_TITLE)) )
titles=([]);
t=titles[gilde];
if ( !t && query_once_interactive(this_object())
&& objectp(g=find_object("/gilden/"+gilde)) )
{
g->adjust_title(this_object());
SetProp(P_TITLE,0);
if ( !mappingp(titles=Query(P_GUILD_TITLE)) )
return 0;
t=titles[gilde];
}
return t;
}
static string _set_guild(string gildenname)
{ object pre;
if (!objectp(pre=previous_object()))
return 0;
if ( pre!=this_object() // Das Lebewesen selber darf die Gilde setzen,
&& object_name(pre)!=GUILDMASTER // der Gildenmaster auch
&& (!this_player()
|| this_player() != this_interactive()
|| !IS_ARCH(this_player())
)
)
return 0;
Set(P_GUILD,gildenname);
GMCP_Char( ([P_GUILD: gildenname]) );
return gildenname;
}
static string _query_guild()
{ string res;
if ( !(res=Query(P_GUILD)) && query_once_interactive(this_object()) )
{
// Spieler, die keiner Gilde angehoeren, gehoeren zur Abenteurergilde
if ( !(res=QueryProp(P_DEFAULT_GUILD)) )
return DEFAULT_GUILD;
else
Set(P_GUILD,res);
return res;
}
return res;
}
static string _query_title()
{ string ti;
if ( stringp(ti=Query(P_TITLE)) )
return ti;
return QueryProp(P_GUILD_TITLE);
}
static string _set_title(string t)
{
Set(P_TITLE, t, F_VALUE);
GMCP_Char( ([P_TITLE: t]) );
return t;
}