{ Summary: Configuration and other mud specific functions ## $Id: mudsystem.pas,v 1.12 2004/03/22 14:18:21 druid Exp $ } unit mudsystem; interface uses {$IFDEF Win32} Winsock2, {$ENDIF} {$IFDEF LINUX} Libc, {$ENDIF} SysUtils, Classes, constants, strip, clean, dtypes, FastStringFuncs, util; type GTime = record hour, day, month, year : integer; sunlight : integer; end; GSystem = record admin_email : string; { email address of the administration } mud_name : string; { name of the MUD Grendel is serving } port : integer; { port on which Grendel runs } port6 : integer; { ipv6 port on which Grendel runs } log_all : boolean; { log all player activity? } bind_ip : integer; { IP the server should bind to (when using multiple interfaces) } level_forcepc : integer; { level to force players } level_log : integer; { level to get log messages } lookup_hosts : boolean; { lookup host names of clients? } deny_newconns : boolean; { deny new connections? } deny_newplayers : boolean; { disable 'CREATE', e.g. no new players } max_conns : integer; { max. concurrent connections on this server } show_clan_abbrev : boolean; { show clan name abbreviations in who list(s) } arena_start, arena_end : integer; { vnum start/end of arena (battleground) } user_high, user_cur : integer; end; GSocial = class name : string; char_no_arg, others_no_arg: string; char_found, others_found, vict_found : string; char_auto, others_auto : string; char_object : string; // Xenon (19/Feb/2001) : for objects (e.g. 'lick rapier') others_object : string; end; GDamMessage = class msg : array[1..3] of string; min, max : integer; end; GBattleground = record prize : pointer; lo_range, hi_range : integer; { level range } winner : pointer; { who has won the bg } count : integer; { seconds to start, -1 for running, -2 for no bg} end; GAuction = class item : pointer; seller, buyer : pointer; going : integer; { 1,2, sold} bid : integer; pulse : integer; start : integer; procedure update(); constructor Create(); end; var system_info : GSystem; time_info : GTime; bg_info : GBattleground; socials : GHashTable; dm_msg : GDLinkedList; clean_thread : GCleanThread; timer_thread : TThread; auction_good, auction_evil : GAuction; banned_masks, banned_names : TStringList; { system data } BootTime : TDateTime; mobs_loaded : integer; online_time : string; status : THeapStatus; procedure bugreport(const func, pasfile, bug : string); procedure calculateonline(); procedure loadSystem(); procedure saveSystem(); function isMaskBanned(const host : string) : boolean; function isNameBanned(name : string) : boolean; procedure loadDamage(); procedure loadSocials(); function findSocial(const cmd : string) : GSocial; function checkSocial(c : pointer; const cmd, param : string) : boolean; procedure loadMudState(); procedure saveMudState(); procedure initSystem(); procedure cleanupSystem(); implementation uses commands, chars, player, area, fsys, conns, console, Channels, RegExpr; procedure bugreport(const func, pasfile, bug : string); begin writeConsole('[BUG] ' + func + ' -> ' + bug); end; procedure calculateonline(); var days, hours, minutes : integer; begin days := DiffDays(BootTime, Now); hours := DiffHours(BootTime, Now); minutes := DiffMinutes(BootTime, Now); dec(minutes, 60 * hours); dec(hours, 24 * days); online_time := inttostr(days) + ' day(s), ' + inttostr(hours) + ' hours(s), ' + inttostr(minutes) + ' minutes(s)'; end; procedure loadSystem(); var s,g : string; af : GFileReader; begin { first some defaults } system_info.mud_name := 'Grendel'; system_info.admin_email := 'admin@localhost'; system_info.port := 4444; system_info.port6 := 3333; system_info.lookup_hosts := false; system_info.deny_newconns := false; system_info.deny_newplayers := false; system_info.level_forcepc := LEVEL_HIGHGOD; system_info.level_log := LEVEL_GOD; system_info.bind_ip := INADDR_ANY; system_info.max_conns := 200; if (not fileExists(SystemDir + 'sysdata.dat')) then begin writeConsole('Could not find main system configuration (sysdata.dat), halting.'); Halt(1); end; af := GFileReader.Create(SystemDir + 'sysdata.dat'); repeat s := af.readLine(); g := uppercase(left(s,':')); if (g = 'PORT') then system_info.port:=strtoint(right(s,' ')) else if (g = 'PORT6') then system_info.port6 := strtoint(right(s, ' ')) else if (g = 'NAME') then system_info.mud_name := right(s,' ') else if (g = 'EMAIL') then system_info.admin_email := right(s,' ') else if (g = 'HOSTLOOKUP') then system_info.lookup_hosts:=strtoint(right(s,' '))<>0 else if (g = 'DENYNEWCONNS') then system_info.deny_newconns:=strtoint(right(s,' '))<>0 else if (g = 'DENYNEWPLAYERS') then system_info.deny_newplayers:=strtoint(right(s,' '))<>0 else if (g = 'LEVELFORCEPC') then system_info.level_forcepc:=strtoint(right(s,' ')) else if (g = 'LEVELLOG') then system_info.level_log:=strtoint(right(s,' ')) else if (g = 'BINDIP') then system_info.bind_ip:=inet_addr(pchar(right(s,' '))) else if (g = 'MAXCONNS') then system_info.max_conns := strtoint(right(s, ' ')) else if (g = 'ARENASTART') then system_info.arena_start := strtoint(right(s, ' ')) else if (g = 'ARENAEND') then system_info.arena_end := strtoint(right(s, ' ')) else if (g = 'SHOWCLANABBREV') then system_info.show_clan_abbrev := strtoint(right(s, ' ')) <> 0; until (s = '$') or (af.eof); af.Free(); if (fileExists(SystemDir + 'bans.dat')) then begin af := GFileReader.Create(SystemDir + 'bans.dat'); repeat s := af.readLine(); if (s <> '$') then banned_masks.add(s); until (s = '$') or (af.eof); af.Free(); end; if (fileExists(SystemDir + 'names.dat')) then begin af := GFileReader.Create(SystemDir + 'names.dat'); repeat s := af.readLine(); if (s <> '$') then banned_names.add(lowercase(s)); until (s = '$') or (af.eof); af.Free(); end; end; // Save the current system configuration procedure saveSystem(); var af : GFileWriter; t : TInAddr; a : integer; begin t.s_addr := system_info.bind_ip; if (fileExists(SystemDir + 'sysdata.dat')) then begin af := GFileWriter.Create(SystemDir + 'sysdata.dat'); af.writeLine('Name: ' + system_info.mud_name); af.writeLine('EMail: ' + system_info.admin_email); af.writeLine('Port: ' + IntToStr(system_info.port)); af.writeLine('Port6: ' + IntToStr(system_info.port6)); af.writeLine('DenyNewConns: ' + IntToStr(integer(system_info.deny_newconns))); af.writeLine('DenyNewPlayers: ' + IntToStr(integer(system_info.deny_newplayers))); af.writeLine('HostLookup: ' + IntToStr(integer(system_info.lookup_hosts))); af.writeLine('LevelForcePC: ' + IntToStr(system_info.level_forcepc)); af.writeLine('LevelLog: ' + IntToStr(system_info.level_log)); af.writeLine('BindIP: ' + inet_ntoa(t)); af.writeLine('MaxConns: ' + IntToStr(system_info.max_conns)); af.writeLine('ArenaStart: ' + IntToStr(system_info.arena_start)); af.writeLine('ArenaEnd: ' + IntToStr(system_info.arena_end)); af.writeLine('ShowClanAbbrev: ' + IntToStr(integer(system_info.show_clan_abbrev))); af.writeLine('$'); af.Free(); end; try af := GFileWriter.Create(SystemDir + 'bans.dat'); except exit; end; for a := 0 to pred(banned_masks.count) do af.writeLine(banned_masks[a]); af.writeLine('$'); af.Free(); try af := GFileWriter.Create(SystemDir + 'names.dat'); except exit; end; for a := 0 to pred(banned_names.count) do af.writeLine(banned_names[a]); af.writeLine('$'); af.Free(); end; // Check if mask is banned function isMaskBanned(const host : string) : boolean; var a : integer; begin Result := false; for a := 0 to pred(banned_masks.count) do if (StringMatches(host, banned_masks[a])) then begin Result := true; end; end; // Check if name is banned function isNameBanned(name : string) : boolean; var a : integer; begin Result := false; name := lowercase(name); // exclude any names that contains non-alpha characters for a := 1 to length(name) do if (not (name[a] in ['a'..'z'])) then begin Result := true; exit; end; for a := 0 to pred(banned_names.count) do begin if (ExecRegExpr(banned_names[a], name)) then begin Result := true; exit; end; end; end; // Load the socials procedure loadSocials(); var af : GFileReader; s, g : string; social : GSocial; begin try af := GFileReader.Create(SystemDir + 'socials.dat'); except exit; end; repeat repeat s := af.readLine(); until (uppercase(s) = '#SOCIAL') or (af.eof()); if (af.eof()) then break; social := GSocial.Create(); with social do repeat s := af.readLine(); s := trim(s); g := uppercase(left(s,':')); if (g = 'NAME') then name := uppercase(right(s,' ')) else if (g = 'CHARNOARG') then char_no_arg := right(s,' ') else if (g = 'OTHERSNOARG') then others_no_arg := right(s,' ') else if (g = 'CHARAUTO') then char_auto := right(s,' ') else if (g = 'OTHERSAUTO') then others_auto := right(s,' ') else if (g = 'CHARFOUND') then char_found := right(s,' ') else if (g = 'VICTFOUND') then vict_found := right(s,' ') else if (g = 'OTHERSFOUND') then others_found := right(s,' ') else if (g = 'CHAROBJECT') then char_object := right(s,' ') else if (g = 'OTHERSOBJECT') then others_object := right(s,' '); until (uppercase(s)='#END') or (af.eof()); if (findSocial(social.name) <> nil) then begin writeConsole('duplicate social "' + social.name + '" on line ' + inttostr(af.line) + ', discarding'); social.Free(); end else socials.put(social.name, social); until (af.eof()); af.Free(); end; function findSocial(const cmd : string) : GSocial; begin Result := GSocial(socials.get(cmd)); end; { Xenon 19/Feb/2001 : - added socials on objects - added checks on social-strings (if empty, ignore) to fix odd behaviour i noticed } function checkSocial(c : pointer; const cmd, param : string) : boolean; var social : GSocial; chance : integer; ch, vict : GCharacter; obj : GObject; begin social := findSocial(cmd); if (social = nil) then begin Result := false; exit; end; ch := GCharacter(c); with social do begin vict := ch.room.findChar(ch, param); obj := ch.room.findObject(param); if (obj = nil) then obj := ch.findEquipment(param); if (obj = nil) then obj := ch.findInventory(param); if (length(param)=0) then // no victim, e.g. 'lick' begin if (length(char_no_arg) = 0) then ch.sendBuffer(' ') else act(AT_SOCIAL,char_no_arg,false,ch,nil,vict,TO_CHAR); if (length(others_no_arg) <> 0) then act(AT_SOCIAL,others_no_arg,false,ch,nil,vict,TO_ROOM); end else if vict=ch then // victim yourself, e.g. 'lick self' begin if (length(char_auto) = 0) then ch.sendBuffer(' ') else act(AT_SOCIAL,char_auto,false,ch,nil,vict,TO_CHAR); if (length(others_auto) <> 0) then act(AT_SOCIAL,others_auto,false,ch,nil,vict,TO_ROOM); end else if (obj <> nil) then // victim is object, e.g. 'lick rapier' begin if (length(char_object) = 0) then ch.sendBuffer(' ') else act(AT_SOCIAL,char_object,false,ch,obj,nil,TO_CHAR); if (length(others_object) <> 0) then act(AT_SOCIAL,others_object,false,ch,obj,nil,TO_ROOM); end else if vict=nil then // victim not there, e.g. 'lick blablablabla' act(AT_SOCIAL,'They are not here.',false,ch,nil,nil,TO_CHAR) else begin // victim, e.g. 'lick grimlord' if (length(char_found) = 0) then ch.sendBuffer(' ') else act(AT_SOCIAL,char_found,false,ch,nil,vict,TO_CHAR); if (length(others_found) <> 0) then act(AT_SOCIAL,others_found,false,ch,nil,vict,TO_NOTVICT); if (length(vict_found) <> 0) then act(AT_SOCIAL,vict_found,false,ch,nil,vict,TO_VICT); if ((not ch.IS_NPC)) and (vict.IS_NPC) and (vict.IS_AWAKE) then begin if (ch <> vict) then begin if (not GNPC(vict).context.runSymbol('onEmoteTarget', [integer(vict), integer(ch), name])) then begin chance := random(10); case chance of 1,2,3,4,5,6:begin if (length(vict_found) <> 0) then act(AT_SOCIAL,vict_found,false,vict,nil,ch,TO_VICT); if (length(others_found) <> 0) then act(AT_SOCIAL,others_found,false,vict,nil,ch,TO_NOTVICT); if (length(char_found) = 0) then ch.sendBuffer(' ') else act(AT_SOCIAL,char_found,false,vict,nil,ch,TO_CHAR); end; 7,8:begin // Xenon (19/Feb/2001) : kinda odd, this one ;) interpret(vict,'say Cut it out!'); interpret(vict,'sigh'); end; else begin act(AT_SOCIAL,'$n slaps you.',false,vict,nil,ch,TO_VICT); act(AT_SOCIAL,'$n slaps $N.',false,vict,nil,ch,TO_NOTVICT); act(AT_SOCIAL,'You slap $N.',false,vict,nil,ch,TO_CHAR); end; end; end; end; end; end; end; Result := true; end; // Load damage messages procedure loadDamage(); var af : GFileReader; s : string; dam : GDamMessage; begin try af := GFileReader.Create(SystemDir + 'damage.dat'); except exit; end; repeat s := af.readLine(); if (length(trim(s)) > 0) then begin dam := GDamMessage.Create(); with dam do begin min := strtoint(left(s,' ')); max := strtoint(right(s,' ')); msg[1] := af.readLine(); msg[2] := af.readLine(); msg[3] := af.readLine(); end; dm_msg.insertLast(dam); end; until (af.eof()); af.Free(); end; // Load current mudstate procedure loadMudState(); var af : GFileREader; s : string; area : GArea; begin try af := GFileReader.Create(SystemDir + 'mudstate.dat'); except exit; end; with time_info do begin af.readToken(); hour := af.readInteger(); day := af.readInteger(); month := af.readInteger(); year := af.readInteger(); sunlight := af.readInteger(); end; repeat if (af.eof()) then break; s := af.readLine(); if (s <> '$') then begin area := findArea(s); if (area = nil) then bugreport('loadMudState()', 'mudsystem.pas', 'area ' + s + ' not found') else with area.weather do begin af.readToken(); mmhg := af.readInteger(); change := af.readInteger(); sky := af.readInteger(); temp := af.readInteger(); end; end; until (s = '$'); af.Free(); end; // Save current mudstate (time, weather) procedure saveMudState(); var af : GFileWriter; iterator : GIterator; area : GArea; begin try af := GFileWriter.Create(SystemDir + 'mudstate.dat'); except exit; end; with time_info do af.writeLine('Time: ' + IntToStr(hour) + ' ' + IntToStr(day) + ' ' + IntToStr(month) + ' ' + IntToStr(year) + ' ' + IntToStr(sunlight)); iterator := area_list.iterator(); while (iterator.hasNext()) do begin area := GArea(iterator.next()); if (not area.flags.isBitSet(AREA_PROTO)) then with area do begin af.writeLine(fname); af.writeLine('Weather: ' + IntToStr(weather.mmhg) + ' ' + IntToStr(weather.change) + ' ' + IntToStr(weather.sky) + ' ' + IntToStr(weather.temp)); end; end; iterator.Free(); af.writeLine('$'); af.Free(); end; // GAuction constructor GAuction.Create(); begin inherited Create(); pulse := 0; item := nil; seller := nil; buyer := nil; end; procedure GAuction.update(); var buf : string; begin inc(going); case going of 1,2:begin if (bid > 0) then begin buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name + '$1] $7' + cap(GObject(item).name); if (going = 1) then buf := buf + ' $1is going ONCE to ' else buf := buf + ' $1is going TWICE to '; buf := buf + GCharacter(buyer).name + ' for ' + inttostr(bid) + ' coins.'; to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT); end else begin buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name + '$1] Anyone?$7 ' + cap(GObject(item).name) + '$1 for ' + inttostr(start) + ' coins?'; to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT); end; end; 3:begin if (bid > 0) then begin buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name + '$1] $7' + cap(GObject(item).name); buf := buf + ' $1has been SOLD to ' + GCharacter(buyer).name + ' for ' + inttostr(bid) + ' coins.'; to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT); GObject(item).toChar(buyer); act(AT_REPORT,'You have won the auction! ' + cap(GObject(item).name) + ' at '+ inttostr(bid) + ' coins.', false, buyer, nil, nil, TO_CHAR); dec(GPlayer(buyer).bankgold, bid); inc(GPlayer(seller).bankgold, bid); end else begin buf := '$B$2<Auction> $1[$7' + GCharacter(seller).name + '$1] Due to lack of bidders, auction has been halted.'; to_channel(GCharacter(seller),buf,CHANNEL_AUCTION,AT_REPORT); GObject(item).toChar(seller); end; seller := nil; buyer := nil; item := nil; end; end; end; procedure initSystem(); begin socials := GHashTable.Create(512); socials.setHashFunc(firstHash); dm_msg := GDLinkedList.Create(); auction_good := GAuction.Create(); auction_evil := GAuction.Create(); banned_masks := TStringList.Create(); banned_names := TStringList.Create(); end; procedure cleanupSystem(); begin socials.clear(); socials.Free(); dm_msg.clear(); dm_msg.Free(); auction_good.Free(); auction_evil.Free(); banned_masks.Free(); banned_names.Free(); end; end.