Misc.pm.svn-base
上传用户:market2
上传日期:2018-11-18
资源大小:18786k
文件大小:138k
源码类别:

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Miscellaneous functions
  3. #
  4. #  This software is open source, licensed under the GNU General Public
  5. #  License, version 2.
  6. #  Basically, this means that you're allowed to modify and distribute
  7. #  this software. However, if you distribute modified versions, you MUST
  8. #  also distribute the source code.
  9. #  See http://www.gnu.org/licenses/gpl.html for the full license.
  10. #
  11. #  $Revision$
  12. #  $Id$
  13. #
  14. #########################################################################
  15. ##
  16. # MODULE DESCRIPTION: Miscellaneous functions
  17. #
  18. # This module contains functions that do not belong in any other modules.
  19. # The difference between Misc.pm and Utils.pm is that Misc.pm can have
  20. # dependencies on other Kore modules.
  21. package Misc;
  22. use strict;
  23. use Exporter;
  24. use Carp::Assert;
  25. use Data::Dumper;
  26. use Compress::Zlib;
  27. use base qw(Exporter);
  28. use encoding 'utf8';
  29. use Globals;
  30. use Log qw(message warning error debug);
  31. use Plugins;
  32. use FileParsers;
  33. use Settings;
  34. use Utils;
  35. use Utils::Assert;
  36. use Skill;
  37. use Field;
  38. use Network;
  39. use Network::Send ();
  40. use AI;
  41. use Actor;
  42. use Actor::You;
  43. use Actor::Player;
  44. use Actor::Monster;
  45. use Actor::Party;
  46. use Actor::NPC;
  47. use Actor::Portal;
  48. use Actor::Pet;
  49. use Actor::Slave;
  50. use Actor::Unknown;
  51. use Time::HiRes qw(time usleep);
  52. use Translation;
  53. use Utils::Exceptions;
  54. our @EXPORT = (
  55. # Config modifiers
  56. qw/auth
  57. configModify
  58. bulkConfigModify
  59. setTimeout
  60. saveConfigFile/,
  61. # Debugging
  62. qw/debug_showSpots
  63. visualDump/,
  64. # Field math
  65. qw/calcRectArea
  66. calcRectArea2
  67. checkLineSnipable
  68. checkLineWalkable
  69. checkWallLength
  70. closestWalkableSpot
  71. objectInsideSpell
  72. objectIsMovingTowards
  73. objectIsMovingTowardsPlayer/,
  74. # Inventory management
  75. qw/inInventory
  76. inventoryItemRemoved
  77. storageGet
  78. cardName
  79. itemName
  80. itemNameSimple/,
  81. # File Parsing and Writing
  82. qw/chatLog
  83. shopLog
  84. monsterLog/,
  85. # Logging
  86. qw/itemLog/,
  87. # OS specific
  88. qw/launchURL/,
  89. # Misc
  90. qw/
  91. actorAdded
  92. actorRemoved
  93. actorListClearing
  94. avoidGM_talk
  95. avoidList_talk
  96. avoidList_ID
  97. calcStat
  98. center
  99. charSelectScreen
  100. chatLog_clear
  101. checkAllowedMap
  102. checkFollowMode
  103. checkMonsterCleanness
  104. createCharacter
  105. deal
  106. dealAddItem
  107. drop
  108. dumpData
  109. getEmotionByCommand
  110. getIDFromChat
  111. getNPCName
  112. getPlayerNameFromCache
  113. getPortalDestName
  114. getResponse
  115. getSpellName
  116. headgearName
  117. initUserSeed
  118. itemLog_clear
  119. look
  120. lookAtPosition
  121. manualMove
  122. meetingPosition
  123. objectAdded
  124. objectRemoved
  125. items_control
  126. pickupitems
  127. mon_control
  128. positionNearPlayer
  129. positionNearPortal
  130. printItemDesc
  131. processNameRequestQueue
  132. quit
  133. relog
  134. sendMessage
  135. setSkillUseTimer
  136. setPartySkillTimer
  137. setStatus
  138. countCastOn
  139. stopAttack
  140. stripLanguageCode
  141. switchConfigFile
  142. updateDamageTables
  143. updatePlayerNameCache
  144. useTeleport
  145. top10Listing
  146. whenGroundStatus
  147. whenStatusActive
  148. whenStatusActiveMon
  149. whenStatusActivePL
  150. writeStorageLog
  151. getBestTarget
  152. isSafe/,
  153. # Actor's Actions Text
  154. qw/attack_string
  155. skillCast_string
  156. skillUse_string
  157. skillUseLocation_string
  158. skillUseNoDamage_string
  159. status_string/,
  160. # AI Math
  161. qw/lineIntersection
  162. percent_hp
  163. percent_sp
  164. percent_weight/,
  165. # Misc Functions
  166. qw/avoidGM_near
  167. avoidList_near
  168. compilePortals
  169. compilePortals_check
  170. portalExists
  171. portalExists2
  172. redirectXKoreMessages
  173. monKilled
  174. getActorName
  175. getActorNames
  176. findPartyUserID
  177. getNPCInfo
  178. skillName
  179. checkSelfCondition
  180. checkPlayerCondition
  181. checkMonsterCondition
  182. findCartItemInit
  183. findCartItem
  184. makeShop
  185. openShop
  186. closeShop
  187. inLockMap
  188. parseReload/
  189. );
  190. # use SelfLoader; 1;
  191. # __DATA__
  192. sub _checkActorHash($$$$) {
  193. my ($name, $hash, $type, $hashName) = @_;
  194. foreach my $actor (values %{$hash}) {
  195. if (!UNIVERSAL::isa($actor, $type)) {
  196. die "$namenUnblessed item in $hashName list:n" .
  197. Dumper($hash);
  198. }
  199. }
  200. }
  201. # Checks whether the internal state of some variables are correct.
  202. sub checkValidity {
  203. return if (!DEBUG || $ENV{OPENKORE_NO_CHECKVALIDITY});
  204. my ($name) = @_;
  205. $name = "Validity check:" if (!defined $name);
  206. assertClass($char, 'Actor::You') if ($net && $net->getState() == Network::IN_GAME
  207. && $net->isa('Network::XKore'));
  208. assertClass($char, 'Actor::You') if ($char);
  209. return;
  210. _checkActorHash($name, %items, 'Actor::Item', 'item');
  211. _checkActorHash($name, %monsters, 'Actor::Monster', 'monster');
  212. _checkActorHash($name, %players, 'Actor::Player', 'player');
  213. _checkActorHash($name, %pets, 'Actor::Pet', 'pet');
  214. _checkActorHash($name, %npcs, 'Actor::NPC', 'NPC');
  215. _checkActorHash($name, %portals, 'Actor::Portal', 'portals');
  216. }
  217. #######################################
  218. #######################################
  219. ### CATEGORY: Configuration modifiers
  220. #######################################
  221. #######################################
  222. sub auth {
  223. my $user = shift;
  224. my $flag = shift;
  225. if ($flag) {
  226. message TF("Authorized user '%s' for adminn", $user), "success";
  227. } else {
  228. message TF("Revoked admin privilages for user '%s'n", $user), "success";
  229. }
  230. $overallAuth{$user} = $flag;
  231. writeDataFile(Settings::getControlFilename("overallAuth.txt"), %overallAuth);
  232. }
  233. ##
  234. # void configModify(String key, String value, ...)
  235. # key: a key name.
  236. # value: the new value.
  237. #
  238. # Changes the value of the configuration option $key to $value.
  239. # Both %config and config.txt will be updated.
  240. #
  241. # You may also call configModify() with additional optional options:
  242. # `l
  243. # - autoCreate (boolean): Whether the configuration option $key
  244. #                         should be created if it doesn't already exist.
  245. #                         The default is true.
  246. # - silent (boolean): By default, output will be printed, notifying the user
  247. #                     that a config option has been changed. Setting this to
  248. #                     true will surpress that output.
  249. # `l`
  250. sub configModify {
  251. my $key = shift;
  252. my $val = shift;
  253. my %args;
  254. if (@_ == 1) {
  255. $args{silent} = $_[0];
  256. } else {
  257. %args = @_;
  258. }
  259. $args{autoCreate} = 1 if (!exists $args{autoCreate});
  260. Plugins::callHook('configModify', {
  261. key => $key,
  262. val => $val,
  263. additionalOptions => %args
  264. });
  265. if (!$args{silent} && $key !~ /password/i) {
  266. my $oldval = $config{$key};
  267. if (!defined $oldval) {
  268. $oldval = "not set";
  269. }
  270. if (!defined $val) {
  271. message TF("Config '%s' unset (was %s)n", $key, $oldval), "info";
  272. } else {
  273. message TF("Config '%s' set to %s (was %s)n", $key, $val, $oldval), "info";
  274. }
  275. }
  276. if ($args{autoCreate} && !exists $config{$key}) {
  277. my $f;
  278. if (open($f, ">>", Settings::getConfigFilename())) {
  279. print $f "$keyn";
  280. close($f);
  281. }
  282. }
  283. $config{$key} = $val;
  284. saveConfigFile();
  285. }
  286. ##
  287. # bulkConfigModify (r_hash, [silent])
  288. # r_hash: key => value to change
  289. # silent: if set to 1, do not print a message to the console.
  290. #
  291. # like configModify but for more than one value at the same time.
  292. sub bulkConfigModify {
  293. my $r_hash = shift;
  294. my $silent = shift;
  295. my $oldval;
  296. foreach my $key (keys %{$r_hash}) {
  297. Plugins::callHook('configModify', {
  298. key => $key,
  299. val => $r_hash->{$key},
  300. silent => $silent
  301. });
  302. $oldval = $config{$key};
  303. $config{$key} = $r_hash->{$key};
  304. if ($key =~ /password/i) {
  305. message TF("Config '%s' set to %s (was *not-displayed*)n", $key, $r_hash->{$key}), "info" unless ($silent);
  306. } else {
  307. message TF("Config '%s' set to %s (was %s)n", $key, $r_hash->{$key}, $oldval), "info" unless ($silent);
  308. }
  309. }
  310. saveConfigFile();
  311. }
  312. ##
  313. # saveConfigFile()
  314. #
  315. # Writes %config to config.txt.
  316. sub saveConfigFile {
  317. writeDataFileIntact(Settings::getConfigFilename(), %config);
  318. }
  319. sub setTimeout {
  320. my $timeout = shift;
  321. my $time = shift;
  322. message TF("Timeout '%s' set to %s (was %s)n", $timeout, $time, $timeout{$timeout}{timeout}), "info";
  323. $timeout{$timeout}{'timeout'} = $time;
  324. writeDataFileIntact2(Settings::getControlFilename("timeouts.txt"), %timeout);
  325. }
  326. #######################################
  327. #######################################
  328. ### Category: Debugging
  329. #######################################
  330. #######################################
  331. our %debug_showSpots_list;
  332. sub debug_showSpots {
  333. return unless $net->clientAlive();
  334. my $ID = shift;
  335. my $spots = shift;
  336. my $special = shift;
  337. if ($debug_showSpots_list{$ID}) {
  338. foreach (@{$debug_showSpots_list{$ID}}) {
  339. my $msg = pack("C*", 0x20, 0x01) . pack("V", $_);
  340. $net->clientSend($msg);
  341. }
  342. }
  343. my $i = 1554;
  344. $debug_showSpots_list{$ID} = [];
  345. foreach (@{$spots}) {
  346. next if !defined $_;
  347. my $msg = pack("C*", 0x1F, 0x01)
  348. . pack("V*", $i, 1550)
  349. . pack("v*", $_->{x}, $_->{y})
  350. . pack("C*", 0x93, 0);
  351. $net->clientSend($msg);
  352. $net->clientSend($msg);
  353. push @{$debug_showSpots_list{$ID}}, $i;
  354. $i++;
  355. }
  356. if ($special) {
  357. my $msg = pack("C*", 0x1F, 0x01)
  358. . pack("V*", 1553, 1550)
  359. . pack("v*", $special->{x}, $special->{y})
  360. . pack("C*", 0x83, 0);
  361. $net->clientSend($msg);
  362. $net->clientSend($msg);
  363. push @{$debug_showSpots_list{$ID}}, 1553;
  364. }
  365. }
  366. ##
  367. # visualDump(data [, label])
  368. #
  369. # Show the bytes in $data on screen as hexadecimal.
  370. # Displays the label if provided.
  371. sub visualDump {
  372. my ($msg, $label) = @_;
  373. my $dump;
  374. my $puncations = quotemeta '~!@#$%^&*()_-+=|"'';
  375. no encoding 'utf8';
  376. use bytes;
  377. $dump = "================================================n";
  378. if (defined $label) {
  379. $dump .= sprintf("%-15s [%d bytes]   %sn", $label, length($msg), getFormattedDate(int(time)));
  380. } else {
  381. $dump .= sprintf("%d bytes   %sn", length($msg), getFormattedDate(int(time)));
  382. }
  383. for (my $i = 0; $i < length($msg); $i += 16) {
  384. my $line;
  385. my $data = substr($msg, $i, 16);
  386. my $rawData = '';
  387. for (my $j = 0; $j < length($data); $j++) {
  388. my $char = substr($data, $j, 1);
  389. if (ord($char) < 32 || ord($char) > 126) {
  390. $rawData .= '.';
  391. } else {
  392. $rawData .= substr($data, $j, 1);
  393. }
  394. }
  395. $line = getHex(substr($data, 0, 8));
  396. $line .= '    ' . getHex(substr($data, 8)) if (length($data) > 8);
  397. $line .= ' ' x (50 - length($line)) if (length($line) < 54);
  398. $line .= "    $rawDatan";
  399. $line = sprintf("%3d>  ", $i) . $line;
  400. $dump .= $line;
  401. }
  402. message $dump;
  403. }
  404. #######################################
  405. #######################################
  406. ### CATEGORY: Field math
  407. #######################################
  408. #######################################
  409. ##
  410. # calcRectArea($x, $y, $radius)
  411. # Returns: an array with position hashes. Each has contains an x and a y key.
  412. #
  413. # Creates a rectangle with center ($x,$y) and radius $radius,
  414. # and returns a list of positions of the border of the rectangle.
  415. sub calcRectArea {
  416. my ($x, $y, $radius) = @_;
  417. my (%topLeft, %topRight, %bottomLeft, %bottomRight);
  418. sub capX {
  419. return 0 if ($_[0] < 0);
  420. return $field{width} - 1 if ($_[0] >= $field{width});
  421. return int $_[0];
  422. }
  423. sub capY {
  424. return 0 if ($_[0] < 0);
  425. return $field{height} - 1 if ($_[0] >= $field{height});
  426. return int $_[0];
  427. }
  428. # Get the avoid area as a rectangle
  429. $topLeft{x} = capX($x - $radius);
  430. $topLeft{y} = capY($y + $radius);
  431. $topRight{x} = capX($x + $radius);
  432. $topRight{y} = capY($y + $radius);
  433. $bottomLeft{x} = capX($x - $radius);
  434. $bottomLeft{y} = capY($y - $radius);
  435. $bottomRight{x} = capX($x + $radius);
  436. $bottomRight{y} = capY($y - $radius);
  437. # Walk through the border of the rectangle
  438. # Record the blocks that are walkable
  439. my @walkableBlocks;
  440. for (my $x = $topLeft{x}; $x <= $topRight{x}; $x++) {
  441. if ($field->isWalkable($x, $topLeft{y})) {
  442. push @walkableBlocks, {x => $x, y => $topLeft{y}};
  443. }
  444. }
  445. for (my $x = $bottomLeft{x}; $x <= $bottomRight{x}; $x++) {
  446. if ($field->isWalkable($x, $bottomLeft{y})) {
  447. push @walkableBlocks, {x => $x, y => $bottomLeft{y}};
  448. }
  449. }
  450. for (my $y = $bottomLeft{y} + 1; $y < $topLeft{y}; $y++) {
  451. if ($field->isWalkable($topLeft{x}, $y)) {
  452. push @walkableBlocks, {x => $topLeft{x}, y => $y};
  453. }
  454. }
  455. for (my $y = $bottomRight{y} + 1; $y < $topRight{y}; $y++) {
  456. if ($field->isWalkable($topLeft{x}, $y)) {
  457. push @walkableBlocks, {x => $topRight{x}, y => $y};
  458. }
  459. }
  460. return @walkableBlocks;
  461. }
  462. ##
  463. # calcRectArea2($x, $y, $radius, $minRange)
  464. # Returns: an array with position hashes. Each has contains an x and a y key.
  465. #
  466. # Creates a rectangle with center ($x,$y) and radius $radius,
  467. # and returns a list of positions inside the rectangle that are
  468. # not closer than $minRange to the center.
  469. sub calcRectArea2 {
  470. my ($cx, $cy, $r, $min) = @_;
  471. my @rectangle;
  472. for (my $x = $cx - $r; $x <= $cx + $r; $x++) {
  473. for (my $y = $cy - $r; $y <= $cy + $r; $y++) {
  474. next if distance({x => $cx, y => $cy}, {x => $x, y => $y}) < $min;
  475. push(@rectangle, {x => $x, y => $y});
  476. }
  477. }
  478. return @rectangle;
  479. }
  480. ##
  481. # checkLineSnipable(from, to)
  482. # from, to: references to position hashes.
  483. #
  484. # Check whether you can snipe a target standing at $to,
  485. # from the position $from, without being blocked by any
  486. # obstacles.
  487. sub checkLineSnipable {
  488. return 0 if (!$field);
  489. my $from = shift;
  490. my $to = shift;
  491. # Simulate tracing a line to the location (modified Bresenham's algorithm)
  492. my ($X0, $Y0, $X1, $Y1) = ($from->{x}, $from->{y}, $to->{x}, $to->{y});
  493. my $steep;
  494. my $posX = 1;
  495. my $posY = 1;
  496. if ($X1 - $X0 < 0) {
  497. $posX = -1;
  498. }
  499. if ($Y1 - $Y0 < 0) {
  500. $posY = -1;
  501. }
  502. if (abs($Y0 - $Y1) < abs($X0 - $X1)) {
  503. $steep = 0;
  504. } else {
  505. $steep = 1;
  506. }
  507. if ($steep == 1) {
  508. my $Yt = $Y0;
  509. $Y0 = $X0;
  510. $X0 = $Yt;
  511. $Yt = $Y1;
  512. $Y1 = $X1;
  513. $X1 = $Yt;
  514. }
  515. if ($X0 > $X1) {
  516. my $Xt = $X0;
  517. $X0 = $X1;
  518. $X1 = $Xt;
  519. my $Yt = $Y0;
  520. $Y0 = $Y1;
  521. $Y1 = $Yt;
  522. }
  523. my $dX = $X1 - $X0;
  524. my $dY = abs($Y1 - $Y0);
  525. my $E = 0;
  526. my $dE;
  527. if ($dX) {
  528. $dE = $dY / $dX;
  529. } else {
  530. # Delta X is 0, it only occures when $from is equal to $to
  531. return 1;
  532. }
  533. my $stepY;
  534. if ($Y0 < $Y1) {
  535. $stepY = 1;
  536. } else {
  537. $stepY = -1;
  538. }
  539. my $Y = $Y0;
  540. my $Erate = 0.99;
  541. if (($posY == -1 && $posX == 1) || ($posY == 1 && $posX == -1)) {
  542. $Erate = 0.01;
  543. }
  544. for (my $X=$X0;$X<=$X1;$X++) {
  545. $E += $dE;
  546. if ($steep == 1) {
  547. return 0 if (!$field->isSnipable($Y, $X));
  548. } else {
  549. return 0 if (!$field->isSnipable($X, $Y));
  550. }
  551. if ($E >= $Erate) {
  552. $Y += $stepY;
  553. $E -= 1;
  554. }
  555. }
  556. return 1;
  557. }
  558. ##
  559. # checkLineWalkable(from, to, [min_obstacle_size = 5])
  560. # from, to: references to position hashes.
  561. #
  562. # Check whether you can walk from $from to $to in an (almost)
  563. # straight line, without obstacles that are too large.
  564. # Obstacles are considered too large, if they are at least
  565. # the size of a rectangle with "radius" $min_obstacle_size.
  566. sub checkLineWalkable {
  567. return 0 if (!$field);
  568. my $from = shift;
  569. my $to = shift;
  570. my $min_obstacle_size = shift;
  571. $min_obstacle_size = 5 if (!defined $min_obstacle_size);
  572. my $dist = round(distance($from, $to));
  573. my %vec;
  574. getVector(%vec, $to, $from);
  575. # Simulate walking from $from to $to
  576. for (my $i = 1; $i < $dist; $i++) {
  577. my %p;
  578. moveAlongVector(%p, $from, %vec, $i);
  579. $p{x} = int $p{x};
  580. $p{y} = int $p{y};
  581. if ( !$field->isWalkable($p{x}, $p{y}) ) {
  582. # The current spot is not walkable. Check whether
  583. # this the obstacle is small enough.
  584. if (checkWallLength(%p, -1,  0, $min_obstacle_size) || checkWallLength(%p,  1, 0, $min_obstacle_size)
  585.  || checkWallLength(%p,  0, -1, $min_obstacle_size) || checkWallLength(%p,  0, 1, $min_obstacle_size)
  586.  || checkWallLength(%p, -1, -1, $min_obstacle_size) || checkWallLength(%p,  1, 1, $min_obstacle_size)
  587.  || checkWallLength(%p,  1, -1, $min_obstacle_size) || checkWallLength(%p, -1, 1, $min_obstacle_size)) {
  588. return 0;
  589. }
  590. }
  591. }
  592. return 1;
  593. }
  594. sub checkWallLength {
  595. my $pos = shift;
  596. my $dx = shift;
  597. my $dy = shift;
  598. my $length = shift;
  599. my $x = $pos->{x};
  600. my $y = $pos->{y};
  601. my $len = 0;
  602. do {
  603. last if ($x < 0 || $x >= $field{width} || $y < 0 || $y >= $field{height});
  604. $x += $dx;
  605. $y += $dy;
  606. $len++;
  607. } while (!$field->isWalkable($x, $y) && $len < $length);
  608. return $len >= $length;
  609. }
  610. ##
  611. # closestWalkableSpot(r_field, pos)
  612. # r_field: a reference to a field hash.
  613. # pos: reference to a position hash (which contains 'x' and 'y' keys).
  614. # Returns: 1 if %pos has been modified, 0 of not.
  615. #
  616. # If the position specified in $pos is walkable, this function will do nothing.
  617. # If it's not walkable, this function will find the closest position that is walkable (up to 2 blocks away),
  618. # and modify the x and y values in $pos.
  619. sub closestWalkableSpot {
  620. my $field = shift;
  621. my $pos = shift;
  622. foreach my $z ( [0,0], [0,1],[1,0],[0,-1],[-1,0], [-1,1],[1,1],[1,-1],[-1,-1],[0,2],[2,0],[0,-2],[-2,0] ) {
  623. next if !$field->isWalkable($pos->{x} + $z->[0], $pos->{y} + $z->[1]);
  624. $pos->{x} += $z->[0];
  625. $pos->{y} += $z->[1];
  626. return 1;
  627. }
  628. return 0;
  629. }
  630. ##
  631. # objectInsideSpell(object, [ignore_party_members = 1])
  632. # object: reference to a player or monster hash.
  633. #
  634. # Checks whether an object is inside someone else's spell area.
  635. # (Traps are also "area spells").
  636. sub objectInsideSpell {
  637. my $object = shift;
  638. my $ignore_party_members = shift;
  639. $ignore_party_members = 1 if (!defined $ignore_party_members);
  640. my ($x, $y) = ($object->{pos_to}{x}, $object->{pos_to}{y});
  641. foreach (@spellsID) {
  642. my $spell = $spells{$_};
  643. if ((!$ignore_party_members || !$char->{party} || !$char->{party}{users}{$spell->{sourceID}})
  644.   && $spell->{sourceID} ne $accountID
  645.   && $spell->{pos}{x} == $x && $spell->{pos}{y} == $y) {
  646. return 1;
  647. }
  648. }
  649. return 0;
  650. }
  651. ##
  652. # objectIsMovingTowards(object1, object2, [max_variance])
  653. #
  654. # Check whether $object1 is moving towards $object2.
  655. sub objectIsMovingTowards {
  656. my $obj = shift;
  657. my $obj2 = shift;
  658. my $max_variance = (shift || 15);
  659. if (!timeOut($obj->{time_move}, $obj->{time_move_calc})) {
  660. # $obj is still moving
  661. my %vec;
  662. getVector(%vec, $obj->{pos_to}, $obj->{pos});
  663. return checkMovementDirection($obj->{pos}, %vec, $obj2->{pos_to}, $max_variance);
  664. }
  665. return 0;
  666. }
  667. ##
  668. # objectIsMovingTowardsPlayer(object, [ignore_party_members = 1])
  669. #
  670. # Check whether an object is moving towards a player.
  671. sub objectIsMovingTowardsPlayer {
  672. my $obj = shift;
  673. my $ignore_party_members = shift;
  674. $ignore_party_members = 1 if (!defined $ignore_party_members);
  675. if (!timeOut($obj->{time_move}, $obj->{time_move_calc}) && @playersID) {
  676. # Monster is still moving, and there are players on screen
  677. my %vec;
  678. getVector(%vec, $obj->{pos_to}, $obj->{pos});
  679. my $players = $playersList->getItems();
  680. foreach my $player (@{$players}) {
  681. my $ID = $player->{ID};
  682. next if (
  683.      ($ignore_party_members && $char->{party} && $char->{party}{users}{$ID})
  684.   || (defined($player->{name}) && existsInList($config{tankersList}, $player->{name}))
  685.   || $player->{statuses}{"GM Perfect Hide"});
  686. if (checkMovementDirection($obj->{pos}, %vec, $player->{pos}, 15)) {
  687. return 1;
  688. }
  689. }
  690. }
  691. return 0;
  692. }
  693. #########################################
  694. #########################################
  695. ### CATEGORY: Logging
  696. #########################################
  697. #########################################
  698. sub itemLog {
  699. my $crud = shift;
  700. return if (!$config{'itemHistory'});
  701. open ITEMLOG, ">>:utf8", $Settings::item_log_file;
  702. print ITEMLOG "[".getFormattedDate(int(time))."] $crud";
  703. close ITEMLOG;
  704. }
  705. sub chatLog {
  706. my $type = shift;
  707. my $message = shift;
  708. open CHAT, ">>:utf8", $Settings::chat_log_file;
  709. print CHAT "[".getFormattedDate(int(time))."][".uc($type)."] $message";
  710. close CHAT;
  711. }
  712. sub shopLog {
  713. my $crud = shift;
  714. open SHOPLOG, ">>:utf8", $Settings::shop_log_file;
  715. print SHOPLOG "[".getFormattedDate(int(time))."] $crud";
  716. close SHOPLOG;
  717. }
  718. sub monsterLog {
  719. my $crud = shift;
  720. return if (!$config{'monsterLog'});
  721. open MONLOG, ">>:utf8", $Settings::monster_log_file;
  722. print MONLOG "[".getFormattedDate(int(time))."] $crudn";
  723. close MONLOG;
  724. }
  725. #########################################
  726. #########################################
  727. ### CATEGORY: Operating system specific
  728. #########################################
  729. #########################################
  730. ##
  731. # launchURL(url)
  732. #
  733. # Open $url in the operating system's preferred web browser.
  734. sub launchURL {
  735. my $url = shift;
  736. if ($^O eq 'MSWin32') {
  737. require Utils::Win32;
  738. Utils::Win32::ShellExecute(0, undef, $url);
  739. } else {
  740. my $mod = 'use POSIX;';
  741. eval $mod;
  742. # This is a script I wrote for the autopackage project
  743. # It autodetects the current desktop environment
  744. my $detectionScript = <<EOF;
  745. function detectDesktop() {
  746. if [[ "$DISPLAY" = "" ]]; then
  747.                  return 1
  748. fi
  749. local LC_ALL=C
  750. local clients
  751. if ! clients=`xlsclients`; then
  752.                 return 1
  753. fi
  754. if echo "$clients" | grep -qE '(gnome-panel|nautilus|metacity)'; then
  755. echo gnome
  756. elif echo "$clients" | grep -qE '(kicker|slicker|karamba|kwin)'; then
  757.                  echo kde
  758. else
  759.                  echo other
  760. fi
  761. return 0
  762. }
  763. detectDesktop
  764. EOF
  765. my ($r, $w, $desktop);
  766. my $pid = IPC::Open2::open2($r, $w, '/bin/bash');
  767. print $w $detectionScript;
  768. close $w;
  769. $desktop = <$r>;
  770. $desktop =~ s/n//;
  771. close $r;
  772. waitpid($pid, 0);
  773. sub checkCommand {
  774. foreach (split(/:/, $ENV{PATH})) {
  775. return 1 if (-x "$_/$_[0]");
  776. }
  777. return 0;
  778. }
  779. if ($desktop eq "gnome" && checkCommand('gnome-open')) {
  780. launchApp(1, 'gnome-open', $url);
  781. } elsif ($desktop eq "kde") {
  782. launchApp(1, 'kfmclient', 'exec', $url);
  783. } else {
  784. if (checkCommand('firefox')) {
  785. launchApp(1, 'firefox', $url);
  786. } elsif (checkCommand('mozillaa')) {
  787. launchApp(1, 'mozilla', $url);
  788. } else {
  789. $interface->errorDialog(TF("No suitable browser detected. Please launch your favorite browser and go to:n%s", $url));
  790. }
  791. }
  792. }
  793. }
  794. #######################################
  795. #######################################
  796. ### CATEGORY: Other functions
  797. #######################################
  798. #######################################
  799. sub actorAddedRemovedVars {
  800. my ($source) = @_;
  801. # returns (type, list, hash)
  802. if ($source == $itemsList) {
  803. return ('item', @itemsID, %items);
  804. } elsif ($source == $playersList) {
  805. return ('player', @playersID, %players);
  806. } elsif ($source == $monstersList) {
  807. return ('monster', @monstersID, %monsters);
  808. } elsif ($source == $portalsList) {
  809. return ('portal', @portalsID, %portals);
  810. } elsif ($source == $petsList) {
  811. return ('pet', @petsID, %pets);
  812. } elsif ($source == $npcsList) {
  813. return ('npc', @npcsID, %npcs);
  814. } elsif ($source == $slavesList) {
  815. return ('slave', @slavesID, %slaves);
  816. } else {
  817. return (undef, undef, undef);
  818. }
  819. }
  820. sub actorAdded {
  821. my (undef, $source, $arg) = @_;
  822. my ($actor, $index) = @{$arg};
  823. $actor->{binID} = $index;
  824. my ($type, $list, $hash) = actorAddedRemovedVars ($source);
  825. if (defined $type) {
  826. if (DEBUG && scalar(keys %{$hash}) + 1 != $source->size()) {
  827. use Data::Dumper;
  828. my $ol = '';
  829. my $items = $source->getItems();
  830. foreach my $item (@{$items}) {
  831. $ol .= $item->nameIdx . "n";
  832. }
  833. die "$type: " . scalar(keys %{$hash}) . " + 1 != " . $source->size() . "n" .
  834. "List:n" .
  835. Dumper($list) . "n" .
  836. "Hash:n" .
  837. Dumper($hash) . "n" .
  838. "ObjectList:n" .
  839. $ol;
  840. }
  841. assert(binSize($list) + 1 == $source->size()) if DEBUG;
  842. binAdd($list, $actor->{ID});
  843. $hash->{$actor->{ID}} = $actor;
  844. objectAdded($type, $actor->{ID}, $actor);
  845. assert(scalar(keys %{$hash}) == $source->size()) if DEBUG;
  846. assert(binSize($list) == $source->size()) if DEBUG;
  847. }
  848. }
  849. sub actorRemoved {
  850. my (undef, $source, $arg) = @_;
  851. my ($actor, $index) = @{$arg};
  852. my ($type, $list, $hash) = actorAddedRemovedVars ($source);
  853. if (defined $type) {
  854. if (DEBUG && scalar(keys %{$hash}) - 1 != $source->size()) {
  855. use Data::Dumper;
  856. my $ol = '';
  857. my $items = $source->getItems();
  858. foreach my $item (@{$items}) {
  859. $ol .= $item->nameIdx . "n";
  860. }
  861. die "$type:" . scalar(keys %{$hash}) . " - 1 != " . $source->size() . "n" .
  862. "List:n" .
  863. Dumper($list) . "n" .
  864. "Hash:n" .
  865. Dumper($hash) . "n" .
  866. "ObjectList:n" .
  867. $ol;
  868. }
  869. assert(binSize($list) - 1 == $source->size()) if DEBUG;
  870. binRemove($list, $actor->{ID});
  871. delete $hash->{$actor->{ID}};
  872. objectRemoved($type, $actor->{ID}, $actor);
  873. if ($type eq "player") {
  874. binRemove(@venderListsID, $actor->{ID});
  875. delete $venderLists{$actor->{ID}};
  876. }
  877. assert(scalar(keys %{$hash}) == $source->size()) if DEBUG;
  878. assert(binSize($list) == $source->size()) if DEBUG;
  879. }
  880. }
  881. sub actorListClearing {
  882. undef %items;
  883. undef %players;
  884. undef %monsters;
  885. undef %portals;
  886. undef %npcs;
  887. undef %pets;
  888. undef %slaves;
  889. undef @itemsID;
  890. undef @playersID;
  891. undef @monstersID;
  892. undef @portalsID;
  893. undef @npcsID;
  894. undef @petsID;
  895. undef @slavesID;
  896. }
  897. sub avoidGM_talk {
  898. return 0 if ($net->clientAlive() || !$config{avoidGM_talk});
  899. my ($user, $msg) = @_;
  900. # Check whether this "GM" is on the ignore list
  901. # in order to prevent false matches
  902. return 0 if (existsInList($config{avoidGM_ignoreList}, $user));
  903. if ($user =~ /^([a-z]?ro)?-?(Sub)?-?[?GM]?/i || $user =~ /$config{avoidGM_namePattern}/) {
  904. my %args = (
  905. name => $user,
  906. );
  907. Plugins::callHook('avoidGM_talk', %args);
  908. return 1 if ($args{return});
  909. warning T("Disconnecting to avoid GM!n");
  910. main::chatLog("k", TF("*** The GM %s talked to you, auto disconnected ***n", $user));
  911. warning TF("Disconnect for %s seconds...n", $config{avoidGM_reconnect});
  912. relog($config{avoidGM_reconnect}, 1);
  913. return 1;
  914. }
  915. return 0;
  916. }
  917. sub avoidList_talk {
  918. return 0 if ($net->clientAlive() || !$config{avoidList});
  919. my ($user, $msg, $ID) = @_;
  920. if ($avoid{Players}{lc($user)}{disconnect_on_chat} || $avoid{ID}{$ID}{disconnect_on_chat}) {
  921. warning TF("Disconnecting to avoid %s!n", $user);
  922. main::chatLog("k", TF("*** %s talked to you, auto disconnected ***n", $user));
  923. warning TF("Disconnect for %s seconds...n", $config{avoidList_reconnect});
  924. relog($config{avoidList_reconnect}, 1);
  925. return 1;
  926. }
  927. return 0;
  928. }
  929. sub calcStat {
  930. my $damage = shift;
  931. $totaldmg += $damage;
  932. }
  933. ##
  934. # center(string, width, [fill])
  935. #
  936. # This function will center $string within a field $width characters wide,
  937. # using $fill characters for padding on either end of the string for
  938. # centering. If $fill is not specified, a space will be used.
  939. sub center {
  940. my ($string, $width, $fill) = @_;
  941. $fill ||= ' ';
  942. my $left = int(($width - length($string)) / 2);
  943. my $right = ($width - length($string)) - $left;
  944. return $fill x $left . $string . $fill x $right;
  945. }
  946. # Returns: 0 if user chose to quit, 1 if user chose a character, 2 if user created or deleted a character
  947. sub charSelectScreen {
  948. my %plugin_args = (autoLogin => shift);
  949. # A list of character names
  950. my @charNames;
  951. # An array which maps an index in @charNames to an index in @chars
  952. my @charNameIndices;
  953. my $mode;
  954. TOP: {
  955. undef $mode;
  956. @charNames = ();
  957. @charNameIndices = ();
  958. }
  959. for (my $num = 0; $num < @chars; $num++) {
  960. next unless ($chars[$num] && %{$chars[$num]});
  961. if (0) {
  962. # The old (more verbose) message
  963. swrite(
  964. T("-------  Character @< ---------n" .
  965. "Name: @<<<<<<<<<<<<<<<<<<<<<<<<n" .
  966. "Job:  @<<<<<<<      Job Exp: @<<<<<<<n" .
  967. "Lv:   @<<<<<<<      Str: @<<<<<<<<n" .
  968. "J.Lv: @<<<<<<<      Agi: @<<<<<<<<n" .
  969. "Exp:  @<<<<<<<      Vit: @<<<<<<<<n" .
  970. "HP:   @||||/@||||   Int: @<<<<<<<<n" .
  971. "SP:   @||||/@||||   Dex: @<<<<<<<<n" .
  972. "Zenny: @<<<<<<<<<<  Luk: @<<<<<<<<n" .
  973. "-------------------------------"),
  974. $num, $chars[$num]{'name'}, $jobs_lut{$chars[$num]{'jobID'}}, $chars[$num]{'exp_job'},
  975. $chars[$num]{'lv'}, $chars[$num]{'str'}, $chars[$num]{'lv_job'}, $chars[$num]{'agi'},
  976. $chars[$num]{'exp'}, $chars[$num]{'vit'}, $chars[$num]{'hp'}, $chars[$num]{'hp_max'},
  977. $chars[$num]{'int'}, $chars[$num]{'sp'}, $chars[$num]{'sp_max'}, $chars[$num]{'dex'},
  978. $chars[$num]{'zenny'}, $chars[$num]{'luk'});
  979. }
  980. push @charNames, TF("Slot %d: %s (%s, level %d/%d)",
  981. $num,
  982. $chars[$num]{name},
  983. $jobs_lut{$chars[$num]{'jobID'}},
  984. $chars[$num]{lv},
  985. $chars[$num]{lv_job});
  986. push @charNameIndices, $num;
  987. }
  988. if (@charNames) {
  989. message(TF("------------- Character List -------------n" .
  990.            "%sn" .
  991.            "------------------------------------------n",
  992.            join("n", @charNames)),
  993.            "connection");
  994. }
  995. return 1 if $net->clientAlive;
  996. Plugins::callHook('charSelectScreen', %plugin_args);
  997. return $plugin_args{return} if ($plugin_args{return});
  998. if ($plugin_args{autoLogin} && @chars && $config{char} ne "" && $chars[$config{char}]) {
  999. $messageSender->sendCharLogin($config{char});
  1000. $timeout{charlogin}{time} = time;
  1001. return 1;
  1002. }
  1003. if (@chars) {
  1004. my @choices = @charNames;
  1005. push @choices, (T('Create a new character'), T('Delete a character'));
  1006. my $choice = $interface->showMenu(
  1007. T("Please choose a character or an action."), @choices,
  1008. title => T("Character selection"));
  1009. if ($choice == -1) {
  1010. # User cancelled
  1011. quit();
  1012. return 0;
  1013. } elsif ($choice < @charNames) {
  1014. # Character chosen
  1015. configModify('char', $charNameIndices[$choice], 1);
  1016. $messageSender->sendCharLogin($config{char});
  1017. $timeout{charlogin}{time} = time;
  1018. return 1;
  1019. } elsif ($choice == @charNames) {
  1020. # 'Create character' chosen
  1021. $mode = "create";
  1022. } else {
  1023. # 'Delete character' chosen
  1024. $mode = "delete";
  1025. }
  1026. } else {
  1027. message T("There are no characters on this account.n"), "connection";
  1028. $mode = "create";
  1029. }
  1030. if ($mode eq "create") {
  1031. while (1) {
  1032. my $message = T("Please enter the desired properties for your characters, in this form:n" .
  1033. "(slot) "(name)" [ (str) (agi) (vit) (int) (dex) (luk) [ (hairstyle) [(haircolor)] ] ]");
  1034. my $input = $interface->query($message);
  1035. if (!defined($input)) {
  1036. goto TOP;
  1037. } else {
  1038. my @args = parseArgs($input);
  1039. if (@args < 2) {
  1040. $interface->errorDialog(T("You didn't specify enough parameters."), 0);
  1041. next;
  1042. }
  1043. message TF("Creating character "%s" in slot "%s"...n", $args[1], $args[0]), "connection";
  1044. $timeout{charlogin}{time} = time;
  1045. last if (createCharacter(@args));
  1046. }
  1047. }
  1048. } elsif ($mode eq "delete") {
  1049. my $choice = $interface->showMenu(
  1050. T("Select the character you want to delete."),
  1051. @charNames,
  1052. title => T("Delete character"));
  1053. if ($choice == -1) {
  1054. goto TOP;
  1055. }
  1056. my $charIndex = @charNameIndices[$choice];
  1057. my $email = $interface->query("Enter your email address.");
  1058. if (!defined($email)) {
  1059. goto TOP;
  1060. }
  1061. my $confirmation = $interface->showMenu(
  1062. TF("Are you ABSOLUTELY SURE you want to delete:n%s", $charNames[$choice]),
  1063. [T("No, don't delete"), T("Yes, delete")],
  1064. title => T("Confirm delete"));
  1065. if ($confirmation != 1) {
  1066. goto TOP;
  1067. }
  1068. $messageSender->sendCharDelete($chars[$charIndex]{charID}, $email);
  1069. message TF("Deleting character %s...n", $chars[$charIndex]{name}), "connection";
  1070. $AI::temp::delIndex = $charIndex;
  1071. $timeout{charlogin}{time} = time;
  1072. }
  1073. return 2;
  1074. }
  1075. sub chatLog_clear {
  1076. if (-f $Settings::chat_log_file) {
  1077. unlink($Settings::chat_log_file);
  1078. }
  1079. }
  1080. ##
  1081. # checkAllowedMap($map)
  1082. #
  1083. # Checks whether $map is in $config{allowedMaps}.
  1084. # Disconnects if it is not, and $config{allowedMaps_reaction} != 0.
  1085. sub checkAllowedMap {
  1086. my $map = shift;
  1087. return unless $AI == 2;
  1088. return unless $config{allowedMaps};
  1089. return if existsInList($config{allowedMaps}, $map);
  1090. return if $config{allowedMaps_reaction} == 0;
  1091. warning TF("The current map (%s) is not on the list of allowed maps.n", $map);
  1092. main::chatLog("k", TF("** The current map (%s) is not on the list of allowed maps.n", $map));
  1093. main::chatLog("k", T("** Exiting...n"));
  1094. quit();
  1095. }
  1096. ##
  1097. # checkFollowMode()
  1098. # Returns: 1 if in follow mode, 0 if not.
  1099. #
  1100. # Check whether we're current in follow mode.
  1101. sub checkFollowMode {
  1102. my $followIndex;
  1103. if ($config{follow} && defined($followIndex = AI::findAction("follow"))) {
  1104. return 1 if (AI::args($followIndex)->{following});
  1105. }
  1106. return 0;
  1107. }
  1108. ##
  1109. # boolean checkMonsterCleanness(Bytes ID)
  1110. # ID: the monster's ID.
  1111. # Requires: $ID is a valid monster ID.
  1112. #
  1113. # Checks whether a monster is "clean" (not being attacked by anyone).
  1114. sub checkMonsterCleanness {
  1115. return 1 if (!$config{attackAuto});
  1116. my $ID = $_[0];
  1117. return 1 if ($playersList->getByID($ID));
  1118. my $monster = $monstersList->getByID($ID);
  1119. # If party attacked monster, or if monster attacked/missed party
  1120. if ($monster->{dmgFromParty} > 0 || $monster->{dmgToParty} > 0 || $monster->{missedToParty} > 0) {
  1121. return 1;
  1122. }
  1123. if ($config{aggressiveAntiKS}) {
  1124. # Aggressive anti-KS mode, for people who are paranoid about not kill stealing.
  1125. # If we attacked the monster first, do not drop it, we are being KSed
  1126. return 1 if ($monster->{dmgFromYou} || $monster->{missedFromYou});
  1127. # If others attacked the monster then always drop it, wether it attacked us or not!
  1128. return 0 if (($monster->{dmgFromPlayer} && %{$monster->{dmgFromPlayer}})
  1129.   || ($monster->{missedFromPlayer} && %{$monster->{missedFromPlayer}})
  1130.   || (($monster->{castOnByPlayer}) && %{$monster->{castOnByPlayer}})
  1131.   || (($monster->{castOnToPlayer}) && %{$monster->{castOnToPlayer}}));
  1132. }
  1133. # If monster attacked/missed you
  1134. return 1 if ($monster->{'dmgToYou'} || $monster->{'missedYou'});
  1135. # If we're in follow mode
  1136. if (defined(my $followIndex = AI::findAction("follow"))) {
  1137. my $following = AI::args($followIndex)->{following};
  1138. my $followID = AI::args($followIndex)->{ID};
  1139. if ($following) {
  1140. # And master attacked monster, or the monster attacked/missed master
  1141. if ($monster->{dmgToPlayer}{$followID} > 0
  1142.  || $monster->{missedToPlayer}{$followID} > 0
  1143.  || $monster->{dmgFromPlayer}{$followID} > 0) {
  1144. return 1;
  1145. }
  1146. }
  1147. }
  1148. if (objectInsideSpell($monster)) {
  1149. # Prohibit attacking this monster in the future
  1150. $monster->{dmgFromPlayer}{$char->{ID}} = 1;
  1151. return 0;
  1152. }
  1153. #check party casting on mob
  1154. my $allowed = 1; 
  1155. if (scalar(keys %{$monster->{castOnByPlayer}}) > 0) 
  1156. foreach (keys %{$monster->{castOnByPlayer}}) 
  1157. my $ID1=$_; 
  1158. my $source = Actor::get($_); 
  1159. unless ( existsInList($config{tankersList}, $source->{name}) || 
  1160. ($char->{party} && %{$char->{party}} && $char->{party}{users}{$ID1} && %{$char->{party}{users}{$ID1}})) 
  1161. $allowed = 0; 
  1162. last; 
  1163. # If monster hasn't been attacked by other players
  1164. if (scalar(keys %{$monster->{missedFromPlayer}}) == 0
  1165.  && scalar(keys %{$monster->{dmgFromPlayer}})    == 0
  1166.  #&& scalar(keys %{$monster->{castOnByPlayer}})   == 0 #change to $allowed
  1167. && $allowed
  1168.  # and it hasn't attacked any other player
  1169.  && scalar(keys %{$monster->{missedToPlayer}}) == 0
  1170.  && scalar(keys %{$monster->{dmgToPlayer}})    == 0
  1171.  && scalar(keys %{$monster->{castOnToPlayer}}) == 0
  1172. ) {
  1173. # The monster might be getting lured by another player.
  1174. # So we check whether it's walking towards any other player, but only
  1175. # if we haven't already attacked the monster.
  1176. if ($monster->{dmgFromYou} || $monster->{missedFromYou}) {
  1177. return 1;
  1178. } else {
  1179. return !objectIsMovingTowardsPlayer($monster);
  1180. }
  1181. }
  1182. # The monster didn't attack you.
  1183. # Other players attacked it, or it attacked other players.
  1184. if ($monster->{dmgFromYou} || $monster->{missedFromYou}) {
  1185. # If you have already attacked the monster before, then consider it clean
  1186. return 1;
  1187. }
  1188. # If you haven't attacked the monster yet, it's unclean.
  1189. return 0;
  1190. }
  1191. ##
  1192. # boolean createCharacter(int slot, String name, int [str,agi,vit,int,dex,luk] = 5)
  1193. # slot: The slot in which to create the character (1st slot is 0).
  1194. # name: The name of the character to create.
  1195. # Returns: Whether the parameters are correct. Only a character creation command
  1196. #          will be sent to the server if all parameters are correct.
  1197. #
  1198. # Create a new character. You must be currently connected to the character login server.
  1199. sub createCharacter {
  1200. my $slot = shift;
  1201. my $name = shift;
  1202. my ($str,$agi,$vit,$int,$dex,$luk, $hair_style, $hair_color) = @_;
  1203. if (!@_) {
  1204. ($str,$agi,$vit,$int,$dex,$luk) = (5,5,5,5,5,5);
  1205. }
  1206. if ($net->getState() != 3) {
  1207. $interface->errorDialog(T("We're not currently connected to the character login server."), 0);
  1208. return 0;
  1209. } elsif ($slot !~ /^d+$/) {
  1210. $interface->errorDialog(TF("Slot "%s" is not a valid number.", $slot), 0);
  1211. return 0;
  1212. } elsif ($slot < 0 || $slot > 4) {
  1213. $interface->errorDialog(T("The slot must be comprised between 0 and 4."), 0);
  1214. return 0;
  1215. } elsif ($chars[$slot]) {
  1216. $interface->errorDialog(TF("Slot %s already contains a character (%s).", $slot, $chars[$slot]{name}), 0);
  1217. return 0;
  1218. } elsif (length($name) > 23) {
  1219. $interface->errorDialog(T("Name must not be longer than 23 characters."), 0);
  1220. return 0;
  1221. } else {
  1222. for ($str,$agi,$vit,$int,$dex,$luk) {
  1223. if ($_ > 9 || $_ < 1) {
  1224. $interface->errorDialog(T("Stats must be comprised between 1 and 9."), 0);
  1225. return;
  1226. }
  1227. }
  1228. for ($str+$int, $agi+$luk, $vit+$dex) {
  1229. if ($_ != 10) {
  1230. $interface->errorDialog(T("The sums Str + Int, Agi + Luk and Vit + Dex must all be equal to 10."), 0);
  1231. return;
  1232. }
  1233. }
  1234. $messageSender->sendCharCreate($slot, $name,
  1235. $str, $agi, $vit, $int, $dex, $luk,
  1236. $hair_style, $hair_color);
  1237. return 1;
  1238. }
  1239. }
  1240. ##
  1241. # void deal(Actor::Player player)
  1242. # Requires: defined($player)
  1243. # Ensures: exists $outgoingDeal{ID}
  1244. #
  1245. # Sends $player a deal request.
  1246. sub deal {
  1247. my $player = $_[0];
  1248. assert(defined $player) if DEBUG;
  1249. assert(UNIVERSAL::isa($player, 'Actor::Player')) if DEBUG;
  1250. $outgoingDeal{ID} = $player->{ID};
  1251. $messageSender->sendDeal($player->{ID});
  1252. }
  1253. ##
  1254. # dealAddItem($item, $amount)
  1255. #
  1256. # Adds $amount of $item to the current deal.
  1257. sub dealAddItem {
  1258. my ($item, $amount) = @_;
  1259. $messageSender->sendDealAddItem($item->{index}, $amount);
  1260. $currentDeal{lastItemAmount} = $amount;
  1261. }
  1262. ##
  1263. # drop(itemIndex, amount)
  1264. #
  1265. # Drops $amount of the item specified by $itemIndex. If $amount is not specified or too large, it defaults
  1266. # to the number of items you have.
  1267. sub drop {
  1268. my ($itemIndex, $amount) = @_;
  1269. my $item = $char->inventory->get($itemIndex);
  1270. if ($item) {
  1271. if (!$amount || $amount > $item->{amount}) {
  1272. $amount = $item->{amount};
  1273. }
  1274. $messageSender->sendDrop($item->{index}, $amount);
  1275. }
  1276. }
  1277. sub dumpData {
  1278. my $msg = shift;
  1279. my $silent = shift;
  1280. my $dump;
  1281. my $puncations = quotemeta '~!@#$%^&*()_+|"'';
  1282. $dump = "nn================================================n" .
  1283. getFormattedDate(int(time)) . "nn" .
  1284. length($msg) . " bytesnn";
  1285. for (my $i = 0; $i < length($msg); $i += 16) {
  1286. my $line;
  1287. my $data = substr($msg, $i, 16);
  1288. my $rawData = '';
  1289. for (my $j = 0; $j < length($data); $j++) {
  1290. my $char = substr($data, $j, 1);
  1291. if (($char =~ /W/ && $char =~ /S/ && !($char =~ /[$puncations]/))
  1292.     || ($char eq chr(10) || $char eq chr(13) || $char eq "t")) {
  1293. $rawData .= '.';
  1294. } else {
  1295. $rawData .= substr($data, $j, 1);
  1296. }
  1297. }
  1298. $line = getHex(substr($data, 0, 8));
  1299. $line .= '    ' . getHex(substr($data, 8)) if (length($data) > 8);
  1300. $line .= ' ' x (50 - length($line)) if (length($line) < 54);
  1301. $line .= "    $rawDatan";
  1302. $line = sprintf("%3d>  ", $i) . $line;
  1303. $dump .= $line;
  1304. }
  1305. open DUMP, ">> DUMP.txt";
  1306. print DUMP $dump;
  1307. close DUMP;
  1308. debug "$dumpn", "parseMsg", 2;
  1309. message T("Message Dumped into DUMP.txt!n"), undef, 1 unless ($silent);
  1310. }
  1311. sub getEmotionByCommand {
  1312. my $command = shift;
  1313. foreach (keys %emotions_lut) {
  1314. if (existsInList($emotions_lut{$_}{command}, $command)) {
  1315. return $_;
  1316. }
  1317. }
  1318. return undef;
  1319. }
  1320. sub getIDFromChat {
  1321. my $r_hash = shift;
  1322. my $msg_user = shift;
  1323. my $match_text = shift;
  1324. my $qm;
  1325. if ($match_text !~ /w+/ || $match_text eq "me" || $match_text eq "") {
  1326. foreach (keys %{$r_hash}) {
  1327. next if ($_ eq "");
  1328. if ($msg_user eq $r_hash->{$_}{name}) {
  1329. return $_;
  1330. }
  1331. }
  1332. } else {
  1333. foreach (keys %{$r_hash}) {
  1334. next if ($_ eq "");
  1335. $qm = quotemeta $match_text;
  1336. if ($r_hash->{$_}{name} =~ /$qm/i) {
  1337. return $_;
  1338. }
  1339. }
  1340. }
  1341. return undef;
  1342. }
  1343. ##
  1344. # getNPCName(ID)
  1345. # ID: the packed ID of the NPC
  1346. # Returns: the name of the NPC
  1347. #
  1348. # Find the name of an NPC: could be NPC, monster, or unknown.
  1349. sub getNPCName {
  1350. my $ID = shift;
  1351. if ((my $npc = $npcsList->getByID($ID))) {
  1352. return $npc->name;
  1353. } elsif ((my $monster = $monstersList->getByID($ID))) {
  1354. return $monster->name;
  1355. } else {
  1356. return "Unknown #" . unpack("V1", $ID);
  1357. }
  1358. }
  1359. ##
  1360. # getPlayerNameFromCache(player)
  1361. # player: an Actor::Player object.
  1362. # Returns: 1 on success, 0 if the player isn't in cache.
  1363. #
  1364. # Retrieve a player's name from cache and modify the player object.
  1365. sub getPlayerNameFromCache {
  1366. my ($player) = @_;
  1367. return if (!$config{cachePlayerNames});
  1368. my $entry = $playerNameCache{$player->{ID}};
  1369. return if (!$entry);
  1370. # Check whether the cache entry is too old or inconsistent.
  1371. # Default cache life time: 15 minutes.
  1372. if (timeOut($entry->{time}, $config{cachePlayerNames_duration}) || $player->{lv} != $entry->{lv} || $player->{jobID} != $entry->{jobID}) {
  1373. binRemove(@playerNameCacheIDs, $player->{ID});
  1374. delete $playerNameCache{$player->{ID}};
  1375. compactArray(@playerNameCacheIDs);
  1376. return 0;
  1377. }
  1378. $player->{name} = $entry->{name};
  1379. $player->{guild} = $entry->{guild} if ($entry->{guild});
  1380. return 1;
  1381. }
  1382. sub getPortalDestName {
  1383. my $ID = shift;
  1384. my %hash; # We only want unique names, so we use a hash
  1385. foreach (keys %{$portals_lut{$ID}{'dest'}}) {
  1386. my $key = $portals_lut{$ID}{'dest'}{$_}{'map'};
  1387. $hash{$key} = 1;
  1388. }
  1389. my @destinations = sort keys %hash;
  1390. return join('/', @destinations);
  1391. }
  1392. sub getResponse {
  1393. my $type = quotemeta shift;
  1394. my @keys;
  1395. foreach my $key (keys %responses) {
  1396. if ($key =~ /^$type_d+$/) {
  1397. push @keys, $key;
  1398. }
  1399. }
  1400. my $msg = $responses{$keys[int(rand(@keys))]};
  1401. $msg =~ s/%$(w+)/$responseVars{$1}/eig;
  1402. return $msg;
  1403. }
  1404. sub getSpellName {
  1405. my $spell = shift;
  1406. return $spells_lut{$spell} || "Unknown $spell";
  1407. }
  1408. ##
  1409. # inInventory($itemName, $quantity = 1)
  1410. #
  1411. # Returns the item's index (can be 0!) if you have at least $quantity units of the item
  1412. # specified by $itemName in your inventory.
  1413. # Returns nothing otherwise.
  1414. sub inInventory {
  1415. my ($itemIndex, $quantity) = @_;
  1416. $quantity ||= 1;
  1417. my $item = $char->inventory->getByName($itemIndex);
  1418. return if !$item;
  1419. return unless $item->{amount} >= $quantity;
  1420. return $item->{invIndex};
  1421. }
  1422. ##
  1423. # inventoryItemRemoved($invIndex, $amount)
  1424. #
  1425. # Removes $amount of $invIndex from $char->{inventory}.
  1426. # Also prints a message saying the item was removed (unless it is an arrow you
  1427. # fired).
  1428. sub inventoryItemRemoved {
  1429. my ($invIndex, $amount) = @_;
  1430. my $item = $char->inventory->get($invIndex);
  1431. if (!$char->{arrow} || ($item && $char->{arrow} != $item->{index})) {
  1432. # This item is not an equipped arrow
  1433. message TF("Inventory Item Removed: %s (%d) x %dn", $item->{name}, $invIndex, $amount), "inventory";
  1434. }
  1435. $item->{amount} -= $amount;
  1436. $char->inventory->remove($item) if ($item->{amount} <= 0);
  1437. $itemChange{$item->{name}} -= $amount;
  1438. }
  1439. # Resolve the name of a card
  1440. sub cardName {
  1441. my $cardID = shift;
  1442. # If card name is unknown, just return ?number
  1443. my $card = $items_lut{$cardID};
  1444. return "?$cardID" if !$card;
  1445. $card =~ s/ Card$//;
  1446. return $card;
  1447. }
  1448. # Resolve the name of a simple item
  1449. sub itemNameSimple {
  1450. my $ID = shift;
  1451. return 'Unknown' unless defined($ID);
  1452. return 'None' unless $ID;
  1453. return $items_lut{$ID} || "Unknown #$ID";
  1454. }
  1455. ##
  1456. # itemName($item)
  1457. #
  1458. # Resolve the name of an item. $item should be a hash with these keys:
  1459. # nameID  => integer index into %items_lut
  1460. # cards   => 8-byte binary data as sent by server
  1461. # upgrade => integer upgrade level
  1462. sub itemName {
  1463. my $item = shift;
  1464. my $name = itemNameSimple($item->{nameID});
  1465. # Resolve item prefix/suffix (carded or forged)
  1466. my $prefix = "";
  1467. my $suffix = "";
  1468. my @cards;
  1469. my %cards;
  1470. for (my $i = 0; $i < 4; $i++) {
  1471. my $card = unpack("v1", substr($item->{cards}, $i*2, 2));
  1472. last unless $card;
  1473. push(@cards, $card);
  1474. ($cards{$card} ||= 0) += 1;
  1475. }
  1476. if ($cards[0] == 254) {
  1477. # Alchemist-made potion
  1478. #
  1479. # Ignore the "cards" inside.
  1480. } elsif ($cards[0] == 65280) {
  1481. # Pet egg
  1482. # cards[0] == 65280
  1483. # substr($item->{cards}, 2, 4) = packed pet ID
  1484. # cards[3] == 1 if named, 0 if not named
  1485. } elsif ($cards[0] == 255) {
  1486. # Forged weapon
  1487. #
  1488. # Display e.g. "VVS Earth" or "Fire"
  1489. my $elementID = $cards[1] % 10;
  1490. my $elementName = $elements_lut{$elementID};
  1491. my $starCrumbs = ($cards[1] >> 8) / 5;
  1492. $prefix .= ('V'x$starCrumbs)."S " if $starCrumbs;
  1493. $prefix .= "$elementName " if ($elementName ne "");
  1494. } elsif (@cards) {
  1495. # Carded item
  1496. #
  1497. # List cards in alphabetical order.
  1498. # Stack identical cards.
  1499. # e.g. "Hydra*2,Mummy*2", "Hydra*3,Mummy"
  1500. $suffix = join(':', map {
  1501. cardName($_).($cards{$_} > 1 ? "*$cards{$_}" : '')
  1502. } sort { cardName($a) cmp cardName($b) } keys %cards);
  1503. }
  1504. my $numSlots = $itemSlotCount_lut{$item->{nameID}} if ($prefix eq "");
  1505. my $display = "";
  1506. $display .= "BROKEN " if $item->{broken};
  1507. $display .= "+$item->{upgrade} " if $item->{upgrade};
  1508. $display .= $prefix if $prefix;
  1509. $display .= $name;
  1510. $display .= " [$suffix]" if $suffix;
  1511. $display .= " [$numSlots]" if $numSlots;
  1512. return $display;
  1513. }
  1514. ##
  1515. # storageGet(items, max)
  1516. # items: reference to an array of storage item hashes.
  1517. # max: the maximum amount to get, for each item, or 0 for unlimited.
  1518. #
  1519. # Get one or more items from storage.
  1520. #
  1521. # Example:
  1522. # # Get items $a and $b from storage.
  1523. # storageGet([$a, $b]);
  1524. # # Get items $a and $b from storage, but at most 30 of each item.
  1525. # storageGet([$a, $b], 30);
  1526. sub storageGet {
  1527. my $indices = shift;
  1528. my $max = shift;
  1529. if (@{$indices} == 1) {
  1530. my ($item) = @{$indices};
  1531. if (!defined($max) || $max > $item->{amount}) {
  1532. $max = $item->{amount};
  1533. }
  1534. $messageSender->sendStorageGet($item->{index}, $max);
  1535. } else {
  1536. my %args;
  1537. $args{items} = $indices;
  1538. $args{max} = $max;
  1539. $args{timeout} = 0.15;
  1540. AI::queue("storageGet", %args);
  1541. }
  1542. }
  1543. ##
  1544. # headgearName(lookID)
  1545. #
  1546. # Resolves a lookID of a headgear into a human readable string.
  1547. #
  1548. # A lookID corresponds to a line number in tables/headgears.txt.
  1549. # The number on that line is the itemID for the headgear.
  1550. sub headgearName {
  1551. my ($lookID) = @_;
  1552. return "Nothing" if $lookID == 0;
  1553. my $itemID = $headgears_lut[$lookID];
  1554. if (!defined($itemID)) {
  1555. return "Unknown lookID $lookID";
  1556. }
  1557. return main::itemName({nameID => $itemID});
  1558. }
  1559. ##
  1560. # void initUserSeed()
  1561. #
  1562. # Generate a unique seed for the current user and save it to
  1563. # a file, or load the seed from that file if it exists.
  1564. sub initUserSeed {
  1565. my $seedFile = "$Settings::logs_folder/seed.txt";
  1566. my $f;
  1567. if (-f $seedFile) {
  1568. if (open($f, "<", $seedFile)) {
  1569. binmode $f;
  1570. $userSeed = <$f>;
  1571. $userSeed =~ s/n.*//s;
  1572. close($f);
  1573. } else {
  1574. $userSeed = '0';
  1575. }
  1576. } else {
  1577. $userSeed = '';
  1578. for (0..10) {
  1579. $userSeed .= rand(2 ** 49);
  1580. }
  1581. if (open($f, ">", $seedFile)) {
  1582. binmode $f;
  1583. print $f $userSeed;
  1584. close($f);
  1585. }
  1586. }
  1587. }
  1588. sub itemLog_clear {
  1589. if (-f $Settings::item_log_file) { unlink($Settings::item_log_file); }
  1590. }
  1591. ##
  1592. # look(bodydir, [headdir])
  1593. # bodydir: a number 0-7. See directions.txt.
  1594. # headdir: 0 = look directly, 1 = look right, 2 = look left
  1595. #
  1596. # Look in the given directions.
  1597. sub look {
  1598. my %args = (
  1599. look_body => shift,
  1600. look_head => shift
  1601. );
  1602. AI::queue("look", %args);
  1603. }
  1604. ##
  1605. # lookAtPosition(pos, [headdir])
  1606. # pos: a reference to a coordinate hash.
  1607. # headdir: 0 = face directly, 1 = look right, 2 = look left
  1608. #
  1609. # Turn face and body direction to position %pos.
  1610. sub lookAtPosition {
  1611. my $pos2 = shift;
  1612. my $headdir = shift;
  1613. my %vec;
  1614. my $direction;
  1615. getVector(%vec, $pos2, $char->{pos_to});
  1616. $direction = int(sprintf("%.0f", (360 - vectorToDegree(%vec)) / 45)) % 8;
  1617. look($direction, $headdir);
  1618. }
  1619. ##
  1620. # manualMove(dx, dy)
  1621. #
  1622. # Moves the character offset from its current position.
  1623. sub manualMove {
  1624. my ($dx, $dy) = @_;
  1625. # Stop following if necessary
  1626. if ($config{'follow'}) {
  1627. configModify('follow', 0);
  1628. AI::clear('follow');
  1629. }
  1630. # Stop moving if necessary
  1631. AI::clear(qw/move route mapRoute/);
  1632. main::ai_route($field{name}, $char->{pos_to}{x} + $dx, $char->{pos_to}{y} + $dy);
  1633. }
  1634. ##
  1635. # meetingPosition(ID, attackMaxDistance)
  1636. # ID: ID of the character to meet.
  1637. # attackMaxDistance: attack distance based on attack method.
  1638. #
  1639. # Returns: the position where the character should go to meet a moving monster.
  1640. sub meetingPosition {
  1641. my ($target, $attackMaxDistance) = @_;
  1642. my $monsterSpeed = ($target->{walk_speed}) ? 1 / $target->{walk_speed} : 0;
  1643. my $timeMonsterMoves = time - $target->{time_move};
  1644. my %monsterPos;
  1645. $monsterPos{x} = $target->{pos}{x};
  1646. $monsterPos{y} = $target->{pos}{y};
  1647. my %monsterPosTo;
  1648. $monsterPosTo{x} = $target->{pos_to}{x};
  1649. $monsterPosTo{y} = $target->{pos_to}{y};
  1650. my %realMonsterPos = calcPosFromTime(%monsterPos, %monsterPosTo, $monsterSpeed, $timeMonsterMoves);
  1651. my $mySpeed = ($char->{walk_speed}) ? 1 / $char->{walk_speed} : 0;
  1652. my $timeCharMoves = time - $char->{time_move};
  1653. my %myPos;
  1654. $myPos{x} = $char->{pos}{x};
  1655. $myPos{y} = $char->{pos}{y};
  1656. my %myPosTo;
  1657. $myPosTo{x} = $char->{pos_to}{x};
  1658. $myPosTo{y} = $char->{pos_to}{y};
  1659. my %realMyPos = calcPosFromTime(%myPos, %myPosTo, $mySpeed, $timeCharMoves);
  1660. my $timeMonsterWalks;
  1661. my $timeCharWalks;
  1662. my %monsterStep;
  1663. my %charStep;
  1664. # There can not be zero step if monster moves
  1665. for (my $monsterStep = 1; $monsterStep <= countSteps(%realMonsterPos, %monsterPosTo); $monsterStep++) {
  1666. # Calculate the steps
  1667. %monsterStep = moveAlong(%realMonsterPos, %monsterPosTo, $monsterStep);
  1668. # Calculate time to walk for monster
  1669. $timeMonsterWalks = calcTime(%realMonsterPos, %monsterStep, $monsterSpeed);
  1670. # Character's route to monsterStep position
  1671. for (my $charStep = 0; $charStep <= countSteps(%realMyPos, %monsterStep); $charStep++) {
  1672. # Calculate the steps
  1673. %charStep = moveAlong(%realMyPos, %monsterStep, $charStep);
  1674. # Check whether the distance is fine
  1675. if (round(distance(%charStep, %monsterStep)) <= $attackMaxDistance) {
  1676. # Calculate time to walk for char
  1677. $timeCharWalks = calcTime(%realMyPos, %charStep, $mySpeed);
  1678. # Check whether character comes earlier or at the same time
  1679. if ($timeCharWalks <= $timeMonsterWalks) {
  1680. return %charStep;
  1681. }
  1682. }
  1683. }
  1684. }
  1685. # If the monster is too fast, move to its pos_to plus attackMaxDistance
  1686. for (my $charStep = 0; $charStep <= countSteps(%realMyPos, %monsterPosTo); $charStep++) {
  1687. # Calculate the steps
  1688. %charStep = moveAlong(%realMyPos, %monsterPosTo, $charStep);
  1689. # Check whether the distance is fine
  1690. if (round(distance(%charStep, %monsterPosTo)) <= $attackMaxDistance) {
  1691. last;
  1692. }
  1693. }
  1694. return %charStep;
  1695. }
  1696. sub objectAdded {
  1697. my ($type, $ID, $obj) = @_;
  1698. if ($type eq 'player' || $type eq 'slave') {
  1699. # Try to retrieve the player name from cache.
  1700. if (!getPlayerNameFromCache($obj)) {
  1701. push @unknownPlayers, $ID;
  1702. }
  1703. } elsif ($type eq 'npc') {
  1704. push @unknownNPCs, $ID;
  1705. }
  1706. if ($type eq 'monster') {
  1707. if (mon_control($obj->{name},$obj->{nameID})->{teleport_search}) {
  1708. $ai_v{temp}{searchMonsters}++;
  1709. }
  1710. }
  1711. Plugins::callHook('objectAdded', {
  1712. type => $type,
  1713. ID => $ID,
  1714. obj => $obj
  1715. });
  1716. }
  1717. sub objectRemoved {
  1718. my ($type, $ID, $obj) = @_;
  1719. if ($type eq 'monster') {
  1720. if (mon_control($obj->{name},$obj->{nameID})->{teleport_search}) {
  1721. $ai_v{temp}{searchMonsters}--;
  1722. }
  1723. }
  1724. Plugins::callHook('objectRemoved', {
  1725. type => $type,
  1726. ID => $ID
  1727. });
  1728. }
  1729. ##
  1730. # items_control($name)
  1731. #
  1732. # Returns the items_control.txt settings for item name $name.
  1733. # If $name has no specific settings, use 'all'.
  1734. sub items_control {
  1735. my ($name) = @_;
  1736. return $items_control{lc($name)} || $items_control{all} || {};
  1737. }
  1738. ##
  1739. # mon_control($name)
  1740. #
  1741. # Returns the mon_control.txt settings for monster name $name.
  1742. # If $name has no specific settings, use 'all'.
  1743. sub mon_control {
  1744. my $name = shift;
  1745. my $nameID = shift;
  1746. return $mon_control{lc($name)} || $mon_control{$nameID} || $mon_control{all} || { attack_auto => 1 };
  1747. }
  1748. ##
  1749. # pickupitems($name)
  1750. #
  1751. # Returns the pickupitems.txt settings for item name $name.
  1752. # If $name has no specific settings, use 'all'.
  1753. sub pickupitems {
  1754. my ($name) = @_;
  1755. return ($pickupitems{lc($name)} ne '') ? $pickupitems{lc($name)} : $pickupitems{all};
  1756. }
  1757. sub positionNearPlayer {
  1758. my $r_hash = shift;
  1759. my $dist = shift;
  1760. my $players = $playersList->getItems();
  1761. foreach my $player (@{$players}) {
  1762. my $ID = $player->{ID};
  1763. next if ($char->{party} && $char->{party}{users} &&
  1764. $char->{party}{users}{$ID});
  1765. next if (defined($player->{name}) && existsInList($config{tankersList}, $player->{name}));
  1766. return 1 if (distance($r_hash, $player->{pos_to}) <= $dist);
  1767. }
  1768. return 0;
  1769. }
  1770. sub positionNearPortal {
  1771. my $r_hash = shift;
  1772. my $dist = shift;
  1773. my $portals = $portalsList->getItems();
  1774. foreach my $portal (@{$portals}) {
  1775. return 1 if (distance($r_hash, $portal->{pos}) <= $dist);
  1776. }
  1777. return 0;
  1778. }
  1779. ##
  1780. # printItemDesc(itemID)
  1781. #
  1782. # Print the description for $itemID.
  1783. sub printItemDesc {
  1784. my $itemID = shift;
  1785. my $itemName = itemNameSimple($itemID);
  1786. my $description = $itemsDesc_lut{$itemID} || T("Error: No description available.n");
  1787. message TF("===============Item Description===============nItem: %snn", $itemName), "info";
  1788. message($description, "info");
  1789. message("==============================================n", "info");
  1790. }
  1791. sub processNameRequestQueue {
  1792. my ($queue, $actorLists, $foo) = @_;
  1793. while (@{$queue}) {
  1794. my $ID = $queue->[0];
  1795. my $actor;
  1796. foreach my $actorList (@$actorLists) {
  1797. last if $actor = $actorList->getByID($ID);
  1798. }
  1799. # Some private servers ban you if you request info for an object with
  1800. # GM Perfect Hide status
  1801. if (!$actor || defined($actor->{name}) || $actor->{statuses}{"GM Perfect Hide"}) {
  1802. shift @{$queue};
  1803. next;
  1804. }
  1805. # Remove actors with a distance greater than clientSight. Some private servers (notably Freya) use
  1806. # a technique where they send actor_exists packets with ridiculous distances in order to automatically
  1807. # ban bots. By removingthose actors, we eliminate that possibility and emulate the client more closely.
  1808. if (defined $actor->{pos_to} && (my $block_dist = blockDistance($char->{pos_to}, $actor->{pos_to})) >= ($config{clientSight} || 16)) {
  1809. debug "Removed actor at $actor->{pos_to}{x} $actor->{pos_to}{y} (distance: $block_dist)n";
  1810. shift @{$queue};
  1811. next;
  1812. }
  1813. $messageSender->sendGetPlayerInfo($ID);
  1814. $actor = shift @{$queue};
  1815. push @{$queue}, $actor if ($actor);
  1816. last;
  1817. }
  1818. }
  1819. sub quit {
  1820. $quit = 1;
  1821. message T("Exiting...n"), "system";
  1822. }
  1823. sub relog {
  1824. my $timeout = (shift || 5);
  1825. my $silent = shift;
  1826. $net->setState(1) if ($net);
  1827. undef $conState_tries;
  1828. $timeout_ex{'master'}{'time'} = time;
  1829. $timeout_ex{'master'}{'timeout'} = $timeout;
  1830. $net->serverDisconnect() if ($net);
  1831. message TF("Relogging in %d seconds...n", $timeout), "connection" unless $silent;
  1832. }
  1833. ##
  1834. # sendMessage(String type, String msg, String user)
  1835. # type: Specifies what kind of message this is. "c" for public chat, "g" for guild chat,
  1836. #       "p" for party chat, "pm" for private message, "k" for messages that only the RO
  1837. #       client will see (in X-Kore mode.)
  1838. # msg: The message to send.
  1839. # user: 
  1840. #
  1841. # Send a chat message to a user.
  1842. sub sendMessage {
  1843. my ($sender, $type, $msg, $user) = @_;
  1844. my ($j, @msgs, $oldmsg, $amount, $space);
  1845. @msgs = split /\n/, $msg;
  1846. for ($j = 0; $j < @msgs; $j++) {
  1847. my (@msg, $i);
  1848. @msg = split / /, $msgs[$j];
  1849. undef $msg;
  1850. for ($i = 0; $i < @msg; $i++) {
  1851. if (!length($msg[$i])) {
  1852. $msg[$i] = " ";
  1853. $space = 1;
  1854. }
  1855. if (length($msg[$i]) > $config{'message_length_max'}) {
  1856. while (length($msg[$i]) >= $config{'message_length_max'}) {
  1857. $oldmsg = $msg;
  1858. if (length($msg)) {
  1859. $amount = $config{'message_length_max'};
  1860. if ($amount - length($msg) > 0) {
  1861. $amount = $config{'message_length_max'} - 1;
  1862. $msg .= " " . substr($msg[$i], 0, $amount - length($msg));
  1863. }
  1864. } else {
  1865. $amount = $config{'message_length_max'};
  1866. $msg .= substr($msg[$i], 0, $amount);
  1867. }
  1868. if ($type eq "c") {
  1869. $sender->sendChat($msg);
  1870. } elsif ($type eq "g") {
  1871. $sender->sendGuildChat($msg);
  1872. } elsif ($type eq "p") {
  1873. $sender->sendPartyChat($msg);
  1874. } elsif ($type eq "pm") {
  1875. $sender->sendPrivateMsg($user, $msg);
  1876. %lastpm = (
  1877. msg => $msg,
  1878. user => $user
  1879. );
  1880. push @lastpm, {%lastpm};
  1881. } elsif ($type eq "k") {
  1882. $sender->injectMessage($msg);
  1883.   }
  1884. $msg[$i] = substr($msg[$i], $amount - length($oldmsg), length($msg[$i]) - $amount - length($oldmsg));
  1885. undef $msg;
  1886. }
  1887. }
  1888. if (length($msg[$i]) && length($msg) + length($msg[$i]) <= $config{'message_length_max'}) {
  1889. if (length($msg)) {
  1890. if (!$space) {
  1891. $msg .= " " . $msg[$i];
  1892. } else {
  1893. $space = 0;
  1894. $msg .= $msg[$i];
  1895. }
  1896. } else {
  1897. $msg .= $msg[$i];
  1898. }
  1899. } else {
  1900. if ($type eq "c") {
  1901. $sender->sendChat($msg);
  1902. } elsif ($type eq "g") {
  1903. $sender->sendGuildChat($msg);
  1904. } elsif ($type eq "p") {
  1905. $sender->sendPartyChat($msg);
  1906. } elsif ($type eq "pm") {
  1907. $sender->sendPrivateMsg($user, $msg);
  1908. %lastpm = (
  1909. msg => $msg,
  1910. user => $user
  1911. );
  1912. push @lastpm, {%lastpm};
  1913. } elsif ($type eq "k") {
  1914. $sender->injectMessage($msg);
  1915. }
  1916. $msg = $msg[$i];
  1917. }
  1918. if (length($msg) && $i == @msg - 1) {
  1919. if ($type eq "c") {
  1920. $sender->sendChat($msg);
  1921. } elsif ($type eq "g") {
  1922. $sender->sendGuildChat($msg);
  1923. } elsif ($type eq "p") {
  1924. $sender->sendPartyChat($msg);
  1925. } elsif ($type eq "pm") {
  1926. $sender->sendPrivateMsg($user, $msg);
  1927. %lastpm = (
  1928. msg => $msg,
  1929. user => $user
  1930. );
  1931. push @lastpm, {%lastpm};
  1932. } elsif ($type eq "k") {
  1933. $sender->injectMessage($msg);
  1934. }
  1935. }
  1936. }
  1937. }
  1938. }
  1939. # Keep track of when we last cast a skill
  1940. sub setSkillUseTimer {
  1941. my ($skillID, $targetID, $wait) = @_;
  1942. my $skill = new Skill(idn => $skillID);
  1943. my $handle = $skill->getHandle();
  1944. $char->{skills}{$handle}{time_used} = time;
  1945. delete $char->{time_cast};
  1946. delete $char->{cast_cancelled};
  1947. $char->{last_skill_time} = time;
  1948. $char->{last_skill_used} = $skillID;
  1949. $char->{last_skill_target} = $targetID;
  1950. # increment monsterSkill maxUses counter
  1951. if (defined $targetID) {
  1952. my $actor = Actor::get($targetID);
  1953. $actor->{skillUses}{$skill->getHandle()}++;
  1954. }
  1955. # Set encore skill if applicable
  1956. $char->{encoreSkill} = $skill if $targetID eq $accountID && $skillsEncore{$skill->getHandle()};
  1957. }
  1958. sub setPartySkillTimer {
  1959. my ($skillID, $targetID) = @_;
  1960. my $skill = new Skill(idn => $skillID);
  1961. my $handle = $skill->getHandle();
  1962. # set partySkill target_time
  1963. my $i = $targetTimeout{$targetID}{$handle};
  1964. $ai_v{"partySkill_${i}_target_time"}{$targetID} = time if $i ne "";
  1965. }
  1966. ##
  1967. # boolean setStatus(Actor actor, param1, param2, param3)
  1968. # param1: the state information of the actor.
  1969. # param2: the ailment information of the actor.
  1970. # param3: the "look" information of the actor.
  1971. # Returns: Whether the actor should be removed from the actor list.
  1972. #
  1973. # Sets the state, ailment, and "look" statuses of the actor.
  1974. # Does not include skillsstatus.txt items.
  1975. sub setStatus {
  1976. my ($actor, $param1, $param2, $param3) = @_;
  1977. my $verbosity = $actor->{ID} eq $accountID ? 1 : 2;
  1978. my $are = $actor->verb('are', 'is');
  1979. my $have = $actor->verb('have', 'has');