grendel-1.0.0a7/backup/
grendel-1.0.0a7/bin/
grendel-1.0.0a7/boards/
grendel-1.0.0a7/clans/
grendel-1.0.0a7/documentation/todo/
grendel-1.0.0a7/help/
grendel-1.0.0a7/logs/
grendel-1.0.0a7/players/
grendel-1.0.0a7/progs/
grendel-1.0.0a7/races/
grendel-1.0.0a7/src/contrib/
grendel-1.0.0a7/src/modules/speller/
grendel-1.0.0a7/src/modules/status/
grendel-1.0.0a7/src/tests/
grendel-1.0.0a7/src/tests/dunit/
{
  Summary:
  	Various skill related functions
  
  ##	$Id: skills.pas,v 1.9 2004/03/21 20:40:03 druid Exp $
}

unit skills;

interface


uses
	SysUtils,
	chars,
	dtypes,
	util,
	constants;


type
	GSkill = class;

	SPEC_FUNC = procedure(ch, victim : GCharacter; sn : GSkill);

	GModifier = record
		apply_type : GApplyTypes;
		modifier : longint;
	end;

	GAffect = class
	private
		_name : PString;      
		_wear_msg : string;
		_duration : integer;

	public
		modifiers : array of GModifier;
		node : GListNode;

		procedure modify(ch : GCharacter; add : boolean);
		procedure applyTo(ch : GCharacter);

		procedure setName(const name : string);
		function getName() : string;

	published
		property name : string read getName write setName;
		property wear_msg : string read _wear_msg write _wear_msg;
		property duration: integer read _duration write _duration;
	end;

	GSkill = class
	private
		_name : PString;

	public      
		id : integer;

		func : SPEC_FUNC;

		affects : GDLinkedList;
		prereqs : GDLinkedList;

		skill_type : integer;
		min_mana : integer;
		min_lvl : integer;
		beats : integer;
		target : integer;

		dicenum, dicesize, diceadd : integer;

		dam_msg : string;
		start_char, start_vict, start_room : string;
		hit_char, hit_vict, hit_room : string;
		miss_char, miss_vict, miss_room : string;
		die_char, die_vict, die_room : string;
		imm_char, imm_vict, imm_room : string;

		constructor Create();
		destructor Destroy(); override;

		procedure setName(const name : string);
		function getName() : string;

	published
		property name : string read getName write setName;    	
	end;


var
   skill_table : GDLinkedList;


{ gsn's }
var
	gsn_slashing_weapons : GSkill;
	gsn_second_attack : GSkill;
	gsn_third_attack : GSkill;
	gsn_fourth_attack : GSkill;
	gsn_fifth_attack : GSkill;
	gsn_enhanced_damage : GSkill;
	gsn_dual_wield : GSkill;
	gsn_slashing : GSkill;
	gsn_piercing : GSkill;
	gsn_concussion : GSkill;
	gsn_whipping : GSkill;
	gsn_kick : GSkill;
	gsn_bash : GSkill;
	gsn_poison : GSkill;
	gsn_sneak : GSkill;
	gsn_swim : GSkill;
	gsn_searching : GSkill;
	gsn_backstab : GSkill;
	gsn_circle : GSkill;
	gsn_rescue : GSkill;
	gsn_dodge : GSkill;
	gsn_track : GSkill;
	gsn_peek : GSkill;
	gsn_hide : GSkill;
	gsn_lockpick : GSkill;


procedure load_skills();

function findSkill(s : string) : GSkill;
function findSkillPlayer(ch : GCharacter; const s : string) : GSkill;

procedure improve_skill(ch : GCharacter; sn : GSkill);
function skill_success(ch : GCharacter; sn : GSkill) : boolean;

function findApply(s : string) : GApplyTypes;
function printApply(apply : GApplyTypes) : string;
function findAffect(ch : GCharacter; const name : string) : GAffect;
procedure removeAffect(ch : GCharacter; aff : GAffect);
function removeAffectName(ch : GCharacter; const name : string):boolean;
function removeAffectFlag(ch : GCharacter; flag : integer):boolean;
procedure update_affects();

procedure initSkills();
procedure cleanupSkills();


implementation


uses
	strip,
	fsys,
	magic,
	fight,
	console,
	player,
	update,
	mudsystem;


// Find skill by name
function findSkill(s : string) : GSkill;
var
	iterator : GIterator;
	sk : GSkill;
begin
  s := trim(uppercase(s));
  Result := nil;
  iterator := skill_table.iterator();

  while (iterator.hasNext()) do
    begin
    sk := GSkill(iterator.next());

    if (s = uppercase(sk.name)) or (pos(s, uppercase(sk.name)) = 1) then
      begin
      Result := sk;
      break;
      end;
    end;
  
  iterator.Free();
end;

function findSkillPlayer(ch : GCharacter; const s : string) : GSkill;
var
   sk : GSkill;
begin
  sk := findSkill(s);
  Result := nil;

  if (ch.LEARNED(sk) > 0) then
    Result := sk;
end;

function assign_gsn(const name : string) : GSkill;
var
   gsn : GSkill;
begin
  gsn := findSkill(name);

  if (gsn = nil) then
    bugreport('assign_gsn', 'skills.pas', 'skill '''+name+''' not found');

  assign_gsn := gsn;
end;

// Load the skills
procedure load_skills();
var
  af : GFileReader;
  s, g, a : string;
  num, x : integer;
  sk, skill : GSkill;
  aff : GAffect;
  modif : integer;
begin
  try
    af := GFileReader.Create('system\skills.dat');
  except
    exit;
  end;

  num := 0;

  repeat
    repeat
      s := af.readLine();
//      writeln('line: ', s);
    until (uppercase(s) = '#SKILL') or (af.eof());

    if (af.eof()) then
      break;

    skill := GSkill.Create();
    skill.id := num;

    with skill do
      repeat
      g := uppercase(left(af.readToken(), ':'));

      if (g = 'TYPE') then
        begin
        s := uppercase(af.readToken());

        if (s = 'SPELL') then
          skill_type := SKILL_SPELL
        else
        if (s = 'SKILL') then
          skill_type := SKILL_SKILL
        else
        if (s = 'WEAPON') then
          skill_type := SKILL_WEAPON;
        end
      else
      if (g = 'NAME') then
        name := af.readLine()
      else
      if (g = 'ROUNDS') then
        beats := af.readInteger()
      else
      if (g = 'MINLEVEL') then
        min_lvl := af.readInteger()
      else
      if (g = 'MANA') then
        min_mana := af.readInteger()
      else
      if (g = 'TARGET') then
        target := af.readInteger()
      else
      if (g = 'FUNCTION') then
        func := findFunc(af.readToken())
      else
      if (g = 'STARTCHAR') then
        start_char := af.readLine()
      else
      if (g = 'STARTVICT') then
        start_vict := af.readLine()
      else
      if (g = 'STARTROOM') then
        start_room := af.readLine()
      else
      if (g = 'HITCHAR') then
        hit_char := af.readLine()
      else
      if (g = 'HITVICT') then
        hit_vict := af.readLine()
      else
      if (g = 'HITROOM') then
        hit_room := af.readLine()
      else
      if (g = 'MISSCHAR') then
        miss_char := af.readLine()
      else
      if (g = 'MISSVICT') then
        miss_vict := af.readLine()
      else
      if (g = 'MISSROOM') then
        miss_room := af.readLine()
      else
      if (g = 'DAMMSG') then
        dam_msg := af.readLine()
      else
      if (g = 'DICE') then
        begin
        a := uppercase(af.readLine());
        dicenum := strtoint(left(a,'D'));
        a := right(a,'D');
        dicesize := strtoint(left(a,'+'));
        a := right(a,'+');
        diceadd := strtoint(left(a,' '));
        end
      else
      if (g = 'AFFECTS') then
        begin
        aff := GAffect.Create();

        aff.name := af.readToken();
        aff.wear_msg := af.readToken();

        aff.duration := af.readInteger();
        x := 1;
              
        while (not af.eol) and (af.readToken = '{') do
          begin
          setLength(aff.modifiers, x);
          
          s := af.readToken();

          aff.modifiers[x - 1].apply_type := findApply(s);

          s := af.readToken();

          modif := StrToIntDef(s, 0);
          
          if (modif = 0) then
            modif := cardinal(hash_string(s));

          aff.modifiers[x - 1].modifier := modif;

          s := af.readToken();
          
          inc(x);
          end;

        aff.node := affects.insertLast(aff);
        end
      else
      if (g = 'PREREQ') then
        begin
        a := af.readToken();
        sk := findSkill(a);

        if (sk <> nil) then
          prereqs.insertLast(sk)
        else
          bugreport('load_skills', 'skills.pas', 'Could not find prereq skill ' + a);
        end;
      until g='#END';

    skill_table.insertLast(skill);

    inc(num);
  until (af.eof());

  af.Free();

  gsn_second_attack := assign_gsn('second attack');
  gsn_third_attack := assign_gsn('third attack');
  gsn_fourth_attack := assign_gsn('fourth attack');
  gsn_fifth_attack := assign_gsn('fifth attack');
  gsn_enhanced_damage := assign_gsn('enhanced damage');
  gsn_dual_wield := assign_gsn('dual wield');
  gsn_slashing := assign_gsn('slashing weapons');
  gsn_piercing := assign_gsn('piercing weapons');
  gsn_concussion := assign_gsn('concussion weapons');
  gsn_whipping := assign_gsn('whipping weapons');
  gsn_kick := assign_gsn('kick');
  gsn_bash := assign_gsn('bash');
  gsn_poison := assign_gsn('poison');
  gsn_sneak := assign_gsn('sneaking');
  gsn_swim := assign_gsn('swim');
  gsn_searching := assign_gsn('searching');
  gsn_backstab := assign_gsn('backstab');
  gsn_circle := assign_gsn('circle');
  gsn_rescue := assign_gsn('rescue');
  gsn_dodge := assign_gsn('dodge');
  gsn_track := assign_gsn('track');
  gsn_peek := assign_gsn('peek');
  gsn_hide := assign_gsn('hide');
  gsn_lockpick := assign_gsn('lockpick');
end;

// Improve skill
procedure improve_skill(ch : GCharacter; sn : GSkill);
var
	chance, percent : integer;
begin
  if (ch.LEARNED(sn) = 100) then
    exit;

  chance := ch.LEARNED(sn) - (ch.wis div 5);

  percent := number_percent;

  if (percent <= chance div 3) then
    begin
    act(AT_WHITE, '[You have become better at ' + sn.name + '!]',false,ch,nil,nil,TO_CHAR);
    ch.SET_LEARNED(UMin(ch.LEARNED(sn) + 1, 100), sn);
    end;
end;

// Check whether a skill is succesful or not
function skill_success(ch : GCharacter; sn : GSkill) : boolean;
begin
  skill_success := (number_percent <= ch.LEARNED(sn));
end;

// Apply modifiers
procedure GAffect.modify(ch : GCharacter; add : boolean);
var
  modif : integer;
  a : integer;
begin
  for a := 0 to length(modifiers) - 1 do
    begin
    modif := modifiers[a].modifier;

    if (not add) then
      begin
      case (modifiers[a].apply_type) of
        APPLY_AFFECT: begin
                      REMOVE_BIT(ch.aff_flags, modif);
                      continue;
                      end;
        APPLY_REMOVE: begin
                      SET_BIT(ch.aff_flags, modif);
                      continue;
                      end;
        APPLY_STRIPNAME: continue;
      end;

      modif := -modif;
      end;

    case (modifiers[a].apply_type) of
      APPLY_STR: ch.str := ch.str + modif;
      APPLY_DEX: ch.dex := ch.dex + modif;
      APPLY_INT: ch.int := ch.int + modif;
      APPLY_WIS: ch.wis := ch.wis + modif;
      APPLY_CON: ch.con := ch.con + modif;
      APPLY_HP: ch.hp := ch.hp + modif;
      APPLY_MAX_HP: ch.max_hp := ch.max_hp + modif;
      APPLY_MV: ch.mv := ch.mv + modif;
      APPLY_MAX_MV: ch.max_mv := ch.max_mv + modif;
      APPLY_MANA: ch.mana := ch.mana + modif;
      APPLY_MAX_MANA: ch.max_mana := ch.max_mana + modif;
      APPLY_AC: begin
                inc(ch.ac_mod, modif);
                ch.calcAC;
                end;
      APPLY_APB: ch.apb := ch.apb + modif;
      APPLY_AFFECT: SET_BIT(ch.aff_flags, modif);
      APPLY_REMOVE: REMOVE_BIT(ch.aff_flags, modif);
      APPLY_STRIPNAME: removeAffectName(ch, PString(modif)^);
      APPLY_FULL: gain_condition(ch, COND_FULL, modif);
      APPLY_THIRST: gain_condition(ch, COND_THIRST, modif);
      APPLY_DRUNK: gain_condition(ch, COND_DRUNK, modif);
      APPLY_CAFFEINE: gain_condition(ch, COND_CAFFEINE, modif);
    end;
  end;
end;

// Apply affect to char
procedure GAffect.applyTo(ch : GCharacter);
var
	aff : GAffect;
begin
  if (duration > 0) then
    begin
    aff := GAffect.Create();
    aff.name := Self.name;
    aff.wear_msg := Self.wear_msg;
    aff.duration := Self.duration;

    aff.modifiers := Self.modifiers;

    if (findAffect(ch, Self.name) = nil) then // not yet affected
      aff.node := ch.affects.insertLast(aff);

    aff.modify(ch, true);
    end
  else
    modify(ch, true);
end;

procedure GAffect.setName(const name : string);
begin 
  if (_name <> nil) then
    unhash_string(_name);
    
  _name := hash_string(name);
end;

function GAffect.getName() : string;
begin
  if (_name <> nil) then
    Result := _name^
  else
    Result := '';
end;

function findApply(s : string) : GApplyTypes;
begin
  s := uppercase(s);

  if (s = 'APPLY_STR') then
    Result := APPLY_STR
  else
  if (s = 'APPLY_DEX') then
    Result := APPLY_DEX
  else
  if (s = 'APPLY_INT') then
    Result := APPLY_INT
  else
  if (s = 'APPLY_WIS') then
    Result := APPLY_WIS
  else
  if (s = 'APPLY_CON') then
    Result := APPLY_CON
  else
  if (s = 'APPLY_HP') then
    Result := APPLY_HP
  else
  if (s = 'APPLY_MAX_HP') then
    Result := APPLY_MAX_HP
  else
  if (s = 'APPLY_MV') then
    Result := APPLY_MV
  else
  if (s = 'APPLY_MAX_MV') then
    Result := APPLY_MAX_MV
  else
  if (s = 'APPLY_MANA') then
    Result := APPLY_MANA
  else
  if (s = 'APPLY_MAX_MANA') then
    Result := APPLY_MAX_MANA
  else
  if (s = 'APPLY_AFFECT') then
    Result := APPLY_AFFECT
  else
  if (s = 'APPLY_REMOVE') then
    Result := APPLY_REMOVE
  else
  if (s = 'APPLY_AC') then
    Result := APPLY_AC
  else
  if (s = 'APPLY_APB') then
    Result := APPLY_APB
  else
  if (s = 'APPLY_STRIPNAME') then
    Result := APPLY_STRIPNAME
  else
  if (s = 'APPLY_FULL') then
    Result := APPLY_FULL
  else
  if (s = 'APPLY_THIRST') then
    Result := APPLY_THIRST
  else
  if (s = 'APPLY_DRUNK') then
    Result := APPLY_DRUNK
  else
  if (s = 'APPLY_CAFFEINE') then
    Result := APPLY_CAFFEINE
  else
    begin
    bugreport('findApply', 'skills.pas', 'Illegal apply type "' + s + '"');
    Result := APPLY_NONE;
    end;
end;

function printApply(apply : GApplyTypes) : string;
begin
  case apply of
    APPLY_STR: Result := 'apply_str';
    APPLY_CON: Result := 'apply_con';
    APPLY_INT: Result := 'apply_int';
    APPLY_WIS: Result := 'apply_wis';
    APPLY_DEX: Result := 'apply_dex';
    APPLY_AC : Result := 'apply_ac';
    APPLY_APB : Result := 'apply_apb';
    APPLY_STRIPNAME : Result := 'apply_stripname';
    APPLY_AFFECT : Result := 'apply_affect';
    APPLY_REMOVE : Result := 'apply_remove';
    APPLY_FULL : Result := 'apply_full';
    APPLY_THIRST : Result := 'apply_thirst';
    APPLY_DRUNK : Result := 'apply_drunk';
    APPLY_CAFFEINE : Result := 'apply_caffeine';
    APPLY_HP : Result := 'apply_hp';
    APPLY_MAX_HP : Result := 'apply_max_hp';
    APPLY_MV : Result := 'apply_mv';
    APPLY_MAX_MV : Result := 'apply_max_mv';
    APPLY_MANA : Result := 'apply_mana';
    APPLY_MAX_MANA : Result := 'apply_max_mana';
    else Result := 'apply_none';
  end;
end;

// Find affect by name on char
function findAffect(ch : GCharacter; const name : string) : GAffect;
var
	iterator : GIterator;
	aff : GAffect;
begin
  Result := nil;

  iterator := ch.affects.iterator();

  while (iterator.hasNext()) do
    begin
    aff := GAffect(iterator.next());

    if (aff.name = name) then
      begin
      Result := aff;
      break;
      end;
    end;
    
  iterator.Free();
end;

// Remove affect <aff> from char
procedure removeAffect(ch : GCharacter; aff : GAffect);
begin
	act(AT_REPORT, aff.wear_msg, false,ch,nil,nil,TO_CHAR);
	
	aff.modify(ch, false);

	ch.affects.remove(aff.node);

	aff.Free();
end;

// Remove affect by name from char
function removeAffectName(ch : GCharacter; const name : string) : boolean;
var
   aff : GAffect;
begin
  Result := false;
  
  if (length(name) = 0) then
    exit;
    
  aff := findAffect(ch, name);

  if (aff = nil) then
    exit;

  removeAffect(ch, aff);

  Result := true;
end;

// Remove affect flag from char
function removeAffectFlag(ch : GCharacter; flag : integer) : boolean;
var
	iterator : GIterator;
	aff, taff : GAffect;
	a : integer;
begin
  removeAffectFlag := false;
  aff := nil;
  iterator := ch.affects.iterator();

  while (iterator.hasNext()) do
    begin
    taff := GAffect(iterator.next());

    for a := 0 to length(taff.modifiers) - 1 do
      begin
      if (taff.modifiers[a].apply_type = APPLY_AFFECT) and (taff.modifiers[a].modifier = flag) then
        begin
        aff := taff;
        break;
        end;
      end;
    end;
    
  iterator.Free();

  if (aff = nil) then
    exit;

  removeAffect(ch, aff);

  removeAffectFlag := true;
end;

// Update affects
procedure update_affects();
var 
	ch : GCharacter;
	aff : GAffect;
	iterator_char, iterator_aff : GIterator;
begin
  iterator_char := char_list.iterator();

  while (iterator_char.hasNext()) do
    begin
    ch := GCharacter(iterator_char.next());

    if (IS_SET(ch.aff_flags,AFF_POISON)) then
      begin
      act(AT_REPORT,'You shiver and suffer.',false,ch,nil,nil,TO_CHAR);
      act(AT_REPORT,'$n shivers and suffers.',false,ch,nil,nil,TO_ROOM);
      ch.mental_state:=URANGE(20,ch.mental_state+4,100);
      damage(ch,ch,6, cardinal(gsn_poison));
      end;
      
{    if IS_SET(ch.aff_flags,AFF_COLD) then
      begin
      i:=random(5);
      case i of
        1:begin
          act(AT_REPORT,'You sneeze loudly... do you need a tissue?',false,ch,nil,nil,TO_CHAR);
          act(AT_REPORT,'$n sneezes loudly... maybe $e needs a tissue?',false,ch,nil,nil,TO_ROOM);
          end;
        2:begin
          act(AT_REPORT,'You sniff a bit.',false,ch,nil,nil,TO_CHAR);
          act(AT_REPORT,'$n sniffs a bit.',false,ch,nil,nil,TO_ROOM);
          end;
        3:begin
          act(AT_REPORT,'You don''t feel very well.',false,ch,nil,nil,TO_CHAR);
          act(AT_REPORT,'$n doesn''t look very well.',false,ch,nil,nil,TO_ROOM);
          ch.mental_state:=URANGE(20,ch.mental_state+1,100);
          end;
      end;
      end; }

    iterator_aff := ch.affects.iterator();

    while (iterator_aff.hasNext()) do
      begin
      aff := GAffect(iterator_aff.next());

      aff.duration := aff.duration - 1;

      if (aff.duration = 0) then
        removeAffect(ch, aff);
      end;
      
    iterator_aff.Free();
    end;
    
	iterator_char.Free();    
end;

{ GSkill }
constructor GSkill.Create();
begin
  inherited Create();

  affects := GDLinkedList.Create();
  
  prereqs := GDLinkedList.Create();
  prereqs.ownsObjects := false;
end;

destructor GSkill.Destroy;
begin
  affects.clear();
  affects.Free();

  prereqs.clear();
  prereqs.Free();
  
  inherited Destroy();
end;

procedure GSkill.setName(const name : string);
begin 
  if (_name <> nil) then
    unhash_string(_name);
    
  _name := hash_string(name);
end;

function GSkill.getName() : string;
begin
  if (_name <> nil) then
    Result := _name^
  else
    Result := '';
end;

procedure initSkills();
begin
  skill_table := GDLinkedList.Create();
end;

procedure cleanupSkills();
begin
  skill_table.clear();
  skill_table.Free();
end;

end.