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

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Field model
  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: 5276 $
  12. #  $Id: Misc.pm 5276 2006-12-28 21:24:00Z vcl_kore $
  13. #
  14. #########################################################################
  15. ##
  16. # MODULE DESCRIPTION: Field model.
  17. #
  18. # The Field class represents a field in the game. A field is a set of blocks.
  19. # each block has a specific type, like 'walkable', 'not walkable', 'water',
  20. # 'cliff', etc.
  21. #
  22. # This class is closely related to the .fld file, as used by OpenKore.
  23. # See http://www.openkore.com/wiki/index.php/Field_file_format#The_FLD_file_format
  24. # for more information.
  25. #
  26. # This class is a hash and has the following hash items:
  27. # `l
  28. # - <tt>name</tt> - The name of the field, like 'prontera'. This is not always the same as baseName.
  29. #                   You should not access this item directly; use the $Field->name() method instead.
  30. # - <tt>baseName</tt> - The name of the field, which is the base name of the file without the extension.
  31. #             This is not always the same as name: for example, in the newbie grounds,
  32. #             the field 'new_1-2' has field file 'new_zone01.fld' and thus base name 'new_zone01'.
  33. # - <tt>width</tt> - The field's width. You should not access this item directly; use $Field->width() instead.
  34. # - <tt>height</tt> - The field's height. You should not access this item directly; use $Field->width() instead.
  35. # - <tt>rawMap</tt> - The raw map data. Contains information about which blocks you can walk on (byte 0),
  36. #                     and which not (byte 1).
  37. # - <tt>dstMap</tt> - The distance map data. Used by pathfinding.
  38. # `l`
  39. package Field;
  40. use strict;
  41. use warnings;
  42. no warnings 'redefine';
  43. use Compress::Zlib;
  44. use File::Spec;
  45. use Globals qw($masterServer %field);
  46. use Modules 'register';
  47. use Settings;
  48. use FastUtils;
  49. use Utils::Exceptions;
  50. # Block types.
  51. use constant {
  52. WALKABLE                        => 0,
  53. NON_WALKABLE                    => 1,
  54. NON_WALKABLE_NON_SNIPABLE_WATER => 2,
  55. WALKABLE_WATER                  => 3,
  56. NON_WALKABLE_SNIPABLE_WATER     => 4,
  57. SNIPABLE_CLIFF                  => 5,
  58. NON_SNIPABLE_CLIFF              => 6,
  59. UNKNOWN                         => 7
  60. };
  61. ##
  62. # Field->new(options...)
  63. #
  64. # Create a new Load a field (.fld) file. This function also loads an associated .dist file
  65. # (the distance map file), which is used by pathfinding (for wall avoidance support).
  66. # If the associated .dist file does not exist, it will be created.
  67. #
  68. # This function also supports gzip-compressed field files (.fld.gz). If the .fld file cannot
  69. # be found, but the corresponding .fld.gz file can be found, this function will load that
  70. # instead and decompress its data on-the-fly.
  71. #
  72. # Allowed options:
  73. #
  74. # `l
  75. # - <tt>file</tt> - The file to load.
  76. # - <tt>name</tt> - The name of the field to load.
  77. # - <tt>loadDistanceMap</tt> (optional) - Whether to also load the distance map. By default, this is true.
  78. # `l`
  79. #
  80. # You must specify either the 'file' option or the 'name' option. If you do not, an
  81. # ArgumentException will be thrown. For example:
  82. # <pre class="example">
  83. # new Field(name => "new_1-1");
  84. # new Field(file => "/path/to/prontera.fld");
  85. # new Field(); # Error: an ArgumentException will be thrown
  86. # </pre>
  87. #
  88. # Throws FileNotFoundException if the field file does not exist (if the 'file' parameter is used),
  89. # or if a field file cannot be found for the specified field name (if the 'name' parameter is used).<br>
  90. # Throws IOException if an error occured while reading the field file.
  91. sub new {
  92. my $class = shift;
  93. my %args = @_;
  94. my $self = bless {}, $class;
  95. if ($args{file}) {
  96. $self->loadFile($args{file}, $args{loadDistanceMap});
  97. } elsif ($args{name}) {
  98. $self->loadByName($args{name}, $args{loadDistanceMap});
  99. } else {
  100. ArgumentException->throw("No field name or filename specified.");
  101. }
  102. return $self;
  103. }
  104. ##
  105. # String $Field->name()
  106. #
  107. # Returns the field's name.
  108. sub name {
  109. return $_[0]->{name};
  110. }
  111. ##
  112. # int $Field->width()
  113. #
  114. # Returns the field's width, in blocks.
  115. sub width {
  116. return $_[0]->{width};
  117. }
  118. ##
  119. # int $Field->height()
  120. #
  121. # Returns the field's height, in blocks.
  122. sub height {
  123. return $_[0]->{height};
  124. }
  125. ##
  126. # int $Field->getBlock(int x, int y)
  127. # x, y: A coordinate on the field.
  128. # Returns: The type for this block. This is one of the block type constants.
  129. #
  130. # Get the type for the block on the specified coordinate. This type is an integer, which
  131. # corresponds with the values specified in the field file format specification:
  132. # http://www.openkore.com/wiki/index.php/Field_file_format#The_FLD_file_format
  133. #
  134. # If you want to check whether the block is walkable, use $field->isWalkable() instead.
  135. sub getBlock {
  136. my ($self, $x, $y) = @_;
  137. if ($x < 0 || $x >= $self->{width} || $y < 0 || $y >= $self->{height}) {
  138. return NON_WALKABLE;
  139. } else {
  140. return ord(substr($self->{rawMap}, ($y * $self->{width}) + $x, 1));
  141. }
  142. }
  143. ##
  144. # boolean $Field->isWalkable(int x, int y)
  145. #
  146. # Check whether you can walk on ($x,$y) on this field.
  147. sub isWalkable {
  148. my $p = &getBlock;
  149. return ($p == WALKABLE || $p == WALKABLE_WATER);
  150. }
  151. ##
  152. # boolean $Field->isSnipable(int x, int y)
  153. #
  154. # Check whether you can snipe through ($x,$y) on this field.
  155. sub isSnipable {
  156. my $p = &getBlock;
  157. return ($p == WALKABLE || $p == WALKABLE_WATER || $p == NON_WALKABLE_SNIPABLE_WATER || $p == SNIPABLE_CLIFF);
  158. }
  159. ##
  160. # void $Field->loadFile(String filename, [boolean loadDistanceMap = true])
  161. # filename: The filename of the field file to load.
  162. # loadDistanceMap: Whether to also load the associated distance map file.
  163. #
  164. # Load the specified field file (.fld file). This is like calling the constructor
  165. # with the 'file' argument, but allows you to load a field inside this Field object.
  166. #
  167. # If $loadDistanceMap is set to false, then $self->{dstMap} will be undef.
  168. #
  169. # Throws FileNotFoundException if the specified file does not exist.
  170. # Throws IOException if a read error occured while reading the field file
  171. # and/or the distance map file.
  172. sub loadFile {
  173. my ($self, $filename, $loadDistanceMap) = @_;
  174. $loadDistanceMap = 1 if (!defined $loadDistanceMap);
  175. $filename =~ s///\/g if ($^O eq 'MSWin32');
  176. if (!-f $filename) {
  177. FileNotFoundException->throw("File $filename does not exist.");
  178. }
  179. # Load the field file.
  180. my ($fieldData, $width, $height);
  181. if ($filename =~ /.gz$/) {
  182. use bytes;
  183. no encoding 'utf8';
  184. my $gz = gzopen($filename, 'rb');
  185. if (!$gz) {
  186. IOException->throw("Cannot open $filename for reading.");
  187. } else {
  188. $fieldData = '';
  189. while (!$gz->gzeof()) {
  190. my $buf;
  191. if ($gz->gzread($buf) >= 0) {
  192. $fieldData .= $buf;
  193. } else {
  194. IOException->throw("An error occured while decompressing $filename.");
  195. }
  196. }
  197. $gz->gzclose();
  198. }
  199. } else {
  200. my $f;
  201. if (open($f, "<", $filename)) {
  202. binmode($f);
  203. local($/);
  204. $fieldData = <$f>;
  205. close($f);
  206. } else {
  207. IOException->throw("Cannot open $filename for reading.");
  208. }
  209. }
  210. ($width, $height) = unpack("v v", substr($fieldData, 0, 4, ''));
  211. # Load the associated distance map (.dist file)
  212. my $distFile = $filename;
  213. $distFile =~ s/.fld(.gz)?$/.dist/i;
  214. if ($loadDistanceMap) {
  215. if (!-f $distFile || !$self->loadDistanceMap($distFile, $width, $height)) {
  216. # (Re)create the distance map.
  217. my $f;
  218. $self->{dstMap} = Utils::makeDistMap($fieldData, $width, $height);
  219. if (open($f, ">", $distFile)) {
  220. binmode $f;
  221. print $f pack("a2 v1", 'V#', 3);
  222. print $f pack("v v", $width, $height);
  223. print $f $self->{dstMap};
  224. close $f;
  225. }
  226. }
  227. } else {
  228. delete $self->{dstMap};
  229. }
  230. $self->{width}  = $width;
  231. $self->{height} = $height;
  232. $self->{rawMap} = $fieldData;
  233. (undef, undef, $self->{baseName}) = File::Spec->splitpath($filename);
  234. $self->{baseName} =~ s/.fld$//i;
  235. $self->{name} = $self->{baseName};
  236. return 1;
  237. }
  238. # boolean $Field->loadDistanceMap(String filename, int width, int height)
  239. # filename: The filename of the distance map.
  240. # width: The width of the field.
  241. # height: The width of the field.
  242. # Requires: The file $filename exists.
  243. # Returns: Whether the distance map file is valid. If it's invalid, then it should be regenerated.
  244. #
  245. # Load a distance map (.dst file). $self->{dstMap} will contain the distance map data.
  246. #
  247. # Throws IOException if a read error occured.
  248. sub loadDistanceMap {
  249. my ($self, $filename, $width, $height) = @_;
  250. my ($f, $distData);
  251. if (open($f, "<", $filename)) {
  252. binmode $f;
  253. local($/);
  254. $distData = <$f>;
  255. close $f;
  256. # Get file version.
  257.   my $dversion = 0;
  258.   if (substr($distData, 0, 2) eq "V#") {
  259.   $dversion = unpack("xx v", substr($distData, 0, 4, ''));
  260.   }
  261.   # Get map width and height.
  262.   my ($dw, $dh) = unpack("v v", substr($distData, 0, 4, ''));
  263. # Version 0 files had a bug when height != width
  264. # Version 1 files did not treat walkable water as walkable, all version 0 and 1 maps need to be rebuilt.
  265. # Version 2 and greater have no know bugs, so just do a minimum validity check.
  266. # Version 3 (the current version) adds better support for walkable water blocks.
  267. # If the distance map version is smaller than 3, regenerate the distance map.
  268. if ($dversion >= 3 && $width == $dw && $height == $dh) {
  269. $self->{dstMap} = $distData;
  270. return 1;
  271. } else {
  272. return 0;
  273. }
  274. } else {
  275. IOException->throw("Cannot open distance map $filename for reading.");
  276. }
  277. }
  278. ##
  279. # void $Field->loadByName(String name, [boolean loadDistanceMap = true])
  280. # name: The name of the field to load. E.g. "prontera".
  281. # loadDistanceMap: Whether to also load the associated distance map file.
  282. #
  283. # Load a field file based on it's name. The actual field file to load is automatically
  284. # determined based on the field name, the field files folder, whether the field file
  285. # is compressed, etc.
  286. #
  287. # This method is like calling the constructor with the 'name' argument,
  288. # but allows you to load a field inside this Field object.
  289. #
  290. # If $loadDistanceMap is set to false, then $self->{dstMap} will be undef.
  291. #
  292. # Throws FileNotFoundException if a field file cannot be found for the specified
  293. # field name.
  294. # Throws IOException if a read error occured while reading the field file
  295. # and/or the distance map file.
  296. sub loadByName {
  297. my ($self, $name, $loadDistanceMap) = @_;
  298. my $file = $self->nameToBaseName($name);
  299. if ($Settings::fields_folder) {
  300. $file = File::Spec->catfile($Settings::fields_folder, $file);
  301. }
  302. if (! -f $file) {
  303. $file .= ".gz";
  304. }
  305. if (-f $file) {
  306. $self->loadFile($file, $loadDistanceMap);
  307. $self->{name} = $name;
  308. } else {
  309. FileNotFoundException->throw("No corresponding field file found for field '$name'.");
  310. }
  311. }
  312. # Map a field name to its field file's base name.
  313. sub nameToBaseName {
  314. my ($self, $name) = @_;
  315. my ($fieldFolder, $baseName);
  316. $fieldFolder = $Settings::fields_folder || ".";
  317. if ($masterServer && $masterServer->{"field_$name"}) {
  318. # Handle server-specific versions of the field.
  319. $baseName = $masterServer->{"field_$name"};
  320. } else {
  321. # Some fields have multiple names, but have the same field data nevertheless.
  322. # For example, the newbie grounds (new_1-1, new_2-1, etc.) all look the same,
  323. # even though they're different fields and may have different monsters.
  324. # Take care of that.
  325. if ($name =~ /^new_d-(d)$/) {
  326. $name = "new_zone0$1";
  327. } elsif ($name =~ /^force_d-(d)$/) {
  328. $name = "force_map$1";
  329. } elsif ($name =~ /^pvp_n_d-2$/) {
  330. $name = "job_hunter";
  331. } elsif ($name =~ /^pvp_n_d-3$/) {
  332. $name = "job_wizard";
  333. } elsif ($name =~ /^pvp_n_d-5$/) {
  334. $name = "job_knight";
  335. }
  336. $baseName = "$name.fld";
  337. }
  338. return $baseName;
  339. }
  340. 1;