/
CVS/
boards/CVS/
clans/
gmc/CVS/
help/CVS/
include/CVS/
players/
progs/CVS/
races/CVS/
system/CVS/
text/
text/CVS/
todo/
todo/CVS/
units/CVS/
// $Id: skills.pas,v 1.15 2001/07/12 16:37:02 druid Exp $

unit skills;

interface

uses
    SysUtils,
    chars,
    dtypes,
    conns,
    util,
    constants;

type
    GSkill = class;

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

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

    GAffect = class
//      skill : GSkill;
      wear_msg : string;
      name : PString;
      duration : longint;
      modifiers : array of GModifier;

      node : GListNode;

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

    GSkill = class
      id : integer;

      func : SPEC_FUNC;

      affects : GDLinkedList;
      prereqs : GDLinkedList;

      name : PString;
      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;
    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;


procedure load_skills;

function findSkill(s : string) : GSkill;
function findSkillPlayer(ch : GCharacter; 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; name : string) : GAffect;
procedure removeAffect(ch : GCharacter; aff : GAffect);
function removeAffectName(ch:GCharacter; name : string):boolean;
function removeAffectFlag(ch:GCharacter; flag : integer):boolean;
procedure update_affects;


implementation

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


function findSkill(s : string) : GSkill;
var
   node : GListNode;
   sk : GSkill;
begin
  s := trim(uppercase(s));
  Result := nil;
  node := skill_table.head;

  while (node <> nil) do
    begin
    sk := node.element;

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

    node := node.next;
    end;
end;

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

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

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

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

  assign_gsn := gsn;
end;

procedure load_skills;
var
  af : GFileReader;
  s,g,a:string;
  num : integer;
  sk, skill : GSkill;
  aff : GAffect;
  modif, len : integer;
begin
  try
    af := GFileReader.Create('system\skills.dat');
  except
    bugreport('load_skills', 'skills.pas', 'could not open system\skills.dat',
              'The system file skills.dat could not be opened.');
    exit;
  end;

  num := 0;

  repeat
    repeat
      s := af.readLine();
    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 := hash_string(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 := hash_string(af.readToken());
        aff.wear_msg := af.readToken();

        aff.duration := af.readInteger();
        num := 1;

        while (not af.eol) and (af.readToken() = '{') do
          begin
          setLength(aff.modifiers, num);

          aff.modifiers[num - 1].apply_type := findApply(af.readToken);

          s := af.readToken();

          modif := cardinal(findSkill(s));

          if (modif = 0) then
            modif := strtointdef(s, 0);

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

          s := af.readToken();

          inc(num);
          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,
                    'The specified skill could not be found.');
        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');
end;

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;

function skill_success(ch : GCharacter; sn : GSkill) : boolean;
begin
  skill_success := (number_percent <= ch.LEARNED(sn));
end;

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);
                      exit;
                      end;
        APPLY_REMOVE: begin
                      SET_BIT(ch.aff_flags, modif);
                      exit;
                      end;
        APPLY_STRIPNAME: exit;
      end;

      modif := -modif;
      end;

    case (modifiers[a].apply_type) of
      APPLY_STR: ch.str := UMin(ch.str + modif, 100);
      APPLY_DEX: ch.dex := UMin(ch.dex + modif, 100);
      APPLY_INT: ch.int := UMin(ch.int + modif, 100);
      APPLY_WIS: ch.wis := UMin(ch.wis + modif, 100);
      APPLY_CON: ch.con := UMin(ch.con + modif, 100);
      APPLY_HP: ch.hp := UMin(ch.hp + modif, ch.max_hp);
      APPLY_MAX_HP: ch.max_hp := UMin(ch.max_hp + modif, 15000);
      APPLY_MV: ch.mv := UMin(ch.mv + modif, ch.max_mv);
      APPLY_MAX_MV: ch.max_mv := UMin(ch.max_mv + modif, 15000);
      APPLY_MANA: ch.mana := UMin(ch.mana + modif, ch.max_mana);
      APPLY_MAX_MANA: ch.max_mana := UMin(ch.max_mana + modif, 15000);
      APPLY_AC: begin
                inc(ch.ac, 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;

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;

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;

function findAffect(ch : GCharacter; name : string) : GAffect;
var
   node : GListNode;
   aff : GAffect;
begin
  Result := nil;

  node := ch.affects.head;

  while (node <> nil) do
    begin
    aff := node.element;

    if (aff.name^ = name) then
      begin
      Result := aff;
      exit;
      end;

    node := node.next;
    end;
end;

procedure removeAffect(ch : GCharacter; aff : GAffect);
begin
  aff.modify(ch, false);

  ch.affects.remove(aff.node);

  aff.Free;
end;

function removeAffectName(ch : GCharacter; name : string) : boolean;
var
   aff : GAffect;
begin
  aff := findAffect(ch, name);

  if (aff = nil) then
    begin
    Result := false;
    exit;
    end;

  removeAffect(ch, aff);

  Result := true;
end;

function removeAffectFlag(ch:GCharacter;flag:integer):boolean;
var
   node : GListNode;
   aff, taff : GAffect;
   a : integer;
begin
  removeAffectFlag := false;
  aff := nil;
  node := ch.affects.head;

  while (node <> nil) do
    begin
    taff := node.element;

    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 := node.element;
        break;
        end;
      end;

    node := node.next;
    end;

  if (aff = nil) then
    exit;

  removeAffect(ch, aff);

  removeAffectFlag := true;
end;

procedure update_affects;
var ch : GCharacter;
    node, node_aff : GListNode;
    aff : GAffect;
begin
  node := char_list.head;

  while (node <> nil) do
    begin
    ch := node.element;

    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; }

    node_aff := ch.affects.head;

    while (node_aff <> nil) do
      begin
      aff := node_aff.element;

      dec(aff.duration);

      if (aff.duration = 0) then
        begin
        act(AT_REPORT, aff.wear_msg, false,ch,nil,nil,TO_CHAR);
        removeAffect(ch, aff);
        end;

      node_aff := node_aff.next;
      end;

    node := node.next;
    end;
end;

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

  affects := GDLinkedList.Create;
  prereqs := GDLinkedList.Create;
end;

destructor GSkill.Destroy;
begin
  affects.clean;
  affects.Free;

  prereqs.smallClean;
  prereqs.Free;
  
  inherited Destroy;
end;

begin
  skill_table := GDLinkedList.Create;
end.