Atrinik Server  4.0
atrinik_map.c
Go to the documentation of this file.
1 /*************************************************************************
2  * Atrinik, a Multiplayer Online Role Playing Game *
3  * *
4  * Copyright (C) 2009-2014 Alex Tokar and Atrinik Development Team *
5  * *
6  * Fork from Crossfire (Multiplayer game for X-windows). *
7  * *
8  * This program is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this program; if not, write to the Free Software *
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
21  * *
22  * The author can be reached at admin@atrinik.org *
23  ************************************************************************/
24 
33 #include <plugin_python.h>
34 #include <player.h>
35 
39 static fields_struct fields[] = {
40  {"next", FIELDTYPE_MAP, offsetof(mapstruct, next), FIELDFLAG_READONLY, 0,
41  "Next map in a doubly-linked list.; Atrinik.Map.Map or None "
42  "(readonly)"},
43  {"previous", FIELDTYPE_MAP, offsetof(mapstruct, prev), FIELDFLAG_READONLY,
44  0, "Previous map in a doubly-linked list.; Atrinik.Map.Map or None "
45  "(readonly)"},
46 
47  {"name", FIELDTYPE_SHSTR, offsetof(mapstruct, name), 0, 0,
48  "Name of the map.; str or None"},
49  {"msg", FIELDTYPE_CSTR, offsetof(mapstruct, msg), 0, 0,
50  "Message map creator may have left.; str or None"},
51  {"reset_timeout", FIELDTYPE_UINT32, offsetof(mapstruct, reset_timeout), 0,
52  0, "How many seconds must elapse before this map should be reset.; "
53  "int"},
54  {"timeout", FIELDTYPE_INT32, offsetof(mapstruct, timeout), 0, 0,
55  "When this reaches 0, the map will be swapped out.; int"},
56  {"difficulty", FIELDTYPE_UINT16, offsetof(mapstruct, difficulty), 0, 0,
57  "What level the player should be to play here. Affects treasures, "
58  "random shops and various other things.; int"},
59  {"height", FIELDTYPE_UINT16, offsetof(mapstruct, height),
60  FIELDFLAG_READONLY, 0, "Height of the map.; int"},
61  {"width", FIELDTYPE_UINT16, offsetof(mapstruct, width),
62  FIELDFLAG_READONLY, 0, "Width of the map.; int"},
63  {"darkness", FIELDTYPE_UINT8, offsetof(mapstruct, darkness), 0, 0,
64  "Indicates the base light value on this map. This value is only "
65  "used when the map is not marked as outdoor.; int"},
66  {"path", FIELDTYPE_SHSTR, offsetof(mapstruct, path), FIELDFLAG_READONLY, 0,
67  "Path to the map file.; str (readonly)"},
68  {"enter_x", FIELDTYPE_UINT8, offsetof(mapstruct, enter_x), 0, 0,
69  "Used to indicate the X position of where to put the player when "
70  "he logs in to the map if the map has :attr:`f_fixed_login` set.;"
71  "int"},
72  {"enter_y", FIELDTYPE_UINT8, offsetof(mapstruct, enter_y), 0, 0,
73  "Used to indicate the Y position of where to put the player when "
74  "he logs in to the map if the map has :attr:`f_fixed_login` set.;"
75  "int"},
76  {"region", FIELDTYPE_REGION, offsetof(mapstruct, region),
77  FIELDFLAG_READONLY, 0, "Region the map is in.; "
78  "Atrinik.Region.Region or None (readonly)"},
79  {"bg_music", FIELDTYPE_SHSTR, offsetof(mapstruct, bg_music), 0, 0,
80  "Background music of the map.; str or None"},
81  {"weather", FIELDTYPE_SHSTR, offsetof(mapstruct, weather), 0, 0,
82  "Weather of the map.; str or None"}
83 };
84 
90 static char *mapflag_names[] = {
91  "f_outdoor", "f_unique", "f_fixed_rtime", "f_nomagic",
92  "f_height_diff", "f_noharm", "f_nosummon", "f_fixed_login",
93  "f_player_no_save", NULL, NULL, NULL, "f_pvp",
94  "f_no_save"
95 };
96 
98 static char *mapflag_docs[] = {
99  "Whether the map is an outdoor map.",
100  "Special unique map.",
101  "If true, reset time is not affected by players entering/leaving the map.",
102  "No spells.",
103  "Height difference will be taken into account when rendering the map.",
104  "No harmful spells like fireball, magic bullet, etc.",
105  "Don't allow any summoning spells.",
106  "When set, a player login on this map will force the player to enter the "
107  "specified :attr:`~Atrinik.Map.enter_x` "
108  ":attr:`~Atrinik.Map.enter_y` coordinates",
109  "Players cannot save on this map.",
110  NULL, NULL, NULL,
111  "PvP is possible on this map.",
112  "Don't save the map - only used with unique maps.",
113 };
114 
116 #define NUM_MAPFLAGS (sizeof(mapflag_names) / sizeof(mapflag_names[0]))
117 
119 static const char doc_Atrinik_Map_Objects[] =
120 ".. method:: Objects(x, y).\n\n"
121 "Iterate objects on the specified map square, from first to last, eg::\n\n"
122 " for obj in Atrinik.WhoIsActivator().map.Objects(0, 0):\n"
123 " print(obj)"
124 "\n\n"
125 ":param x: X coordinate on the map.\n"
126 ":type x: int\n"
127 ":param y: Y coordinate on the map.\n"
128 ":type y: int\n"
129 ":returns: Iterator object.\n"
130 ":rtype: :class:`Atrinik.Object.ObjectIterator`";
131 
136 static PyObject *Atrinik_Map_Objects(Atrinik_Map *self, PyObject *args)
137 {
138  int x, y;
139 
140  if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
141  return NULL;
142  }
143 
144  mapstruct *m = hooks->get_map_from_coord(self->map, &x, &y);
145  if (m != NULL) {
146  /* Since map objects are loaded in reverse mode, the last one in
147  * in the list is actually the first. */
148  return wrap_object_iterator(GET_MAP_OB_LAST(m, x, y));
149  }
150 
151  return wrap_object_iterator(NULL);
152 }
153 
155 static const char doc_Atrinik_Map_ObjectsReversed[] =
156 ".. method:: ObjectsReversed(x, y).\n\n"
157 "Iterate objects on the specified map square, from last to first, eg::\n\n"
158 " for obj in Atrinik.WhoIsActivator().map.ObjectsReversed(0, 0):\n"
159 " print(obj)"
160 "\n\n"
161 ":param x: X coordinate on the map.\n"
162 ":type x: int\n"
163 ":param y: Y coordinate on the map.\n"
164 ":type y: int\n"
165 ":returns: Iterator object.\n"
166 ":rtype: :class:`Atrinik.Object.ObjectIterator`";
167 
172 static PyObject *Atrinik_Map_ObjectsReversed(Atrinik_Map *self, PyObject *args)
173 {
174  int x, y;
175 
176  if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
177  return NULL;
178  }
179 
180  mapstruct *m = hooks->get_map_from_coord(self->map, &x, &y);
181  if (m != NULL) {
182  /* Since map objects are loaded in reverse mode, the first one in
183  * in the list is actually the last. */
184  return wrap_object_iterator(GET_MAP_OB(m, x, y));
185  }
186 
187  return wrap_object_iterator(NULL);
188 }
189 
191 static const char doc_Atrinik_Map_GetLayer[] =
192 ".. method:: GetLayer(x, y, layer, sub_layer=-1).\n\n"
193 "Construct a list containing objects with the specified layer on the specified "
194 "square.\n\nNote that there is another way to loop through objects on a "
195 "square, which is less memory intensive, and can be stopped at any time with a "
196 "break::\n\n"
197 " for obj in Atrinik.WhoIsActivator().map.Objects(0, 0):\n"
198 " print(obj)"
199 "\n\n"
200 ":param x: X coordinate on the map.\n"
201 ":type x: int\n"
202 ":param y: Y coordinate on the map.\n"
203 ":type y: int\n"
204 ":param layer: Layer to look for - one of LAYER_xxx constants, eg, :attr:"
205 "`~Atrinik.LAYER_WALL`.\n"
206 ":type layer: int\n"
207 ":param sub_layer: Sub-layer to look for; if -1, will look for all "
208 "sub-layers.\n"
209 ":type sub_layer: int\n"
210 ":returns: A list containing objects on the square with the specified layer.\n"
211 ":rtype: list of :class:`Atrinik.Object.Object`\n"
212 ":raises ValueError: If invalid layer ID was specified.\n"
213 ":raises Atrinik.AtrinikError: If there was an error trying to get the "
214 "objects (invalid X/Y, or not on a nearby tiled map, for example).";
215 
220 static PyObject *Atrinik_Map_GetLayer(Atrinik_Map *self, PyObject *args)
221 {
222  int x, y;
223  uint8_t layer;
224  int8_t sub_layer = -1;
225 
226  if (!PyArg_ParseTuple(args, "iib|B", &x, &y, &layer, &sub_layer)) {
227  return NULL;
228  }
229 
230  /* Validate the layer ID. */
231  if (layer > NUM_LAYERS) {
232  PyErr_SetString(PyExc_ValueError, "Invalid layer ID.");
233  return NULL;
234  }
235 
236  mapstruct *m = hooks->get_map_from_coord(self->map, &x, &y);
237  if (m == NULL) {
238  RAISE("Unable to get map using get_map_from_coord().");
239  }
240 
241  PyObject *list = PyList_New(0);
242 
243  object *tmp;
244  FOR_MAP_LAYER_BEGIN(m, x, y, layer, sub_layer, tmp) {
245  PyList_Append(list, wrap_object(tmp));
247 
248  return list;
249 }
250 
252 static const char doc_Atrinik_Map_GetMapFromCoord[] =
253 ".. method:: GetMapFromCoord(x, y).\n\n"
254 "Get real coordinates from map, taking tiling into consideration.\n\n"
255 ":param x: X coordinate on the map.\n"
256 ":type x: int\n"
257 ":param y: Y coordinate on the map.\n"
258 ":type y: int\n"
259 ":returns: A tuple containing new map, new X, and new Y to use. The new map "
260 "can be None.\n"
261 ":rtype: tuple";
262 
267 static PyObject *Atrinik_Map_GetMapFromCoord(Atrinik_Map *self, PyObject *args)
268 {
269  int x, y;
270 
271  if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
272  return NULL;
273  }
274 
275  mapstruct *m = hooks->get_map_from_coord(self->map, &x, &y);
276  PyObject *tuple = PyTuple_New(3);
277  PyTuple_SET_ITEM(tuple, 0, wrap_map(m));
278  PyTuple_SET_ITEM(tuple, 1, Py_BuildValue("i", x));
279  PyTuple_SET_ITEM(tuple, 2, Py_BuildValue("i", y));
280 
281  return tuple;
282 }
283 
285 static const char doc_Atrinik_Map_PlaySound[] =
286 ".. method:: PlaySound(filename, x, y, type=Atrinik.CMD_SOUND_EFFECT, loop=0, "
287 "volume=0).\n\n"
288 "Play a sound on the map.\n\n"
289 ":param filename: Sound file to play.\n"
290 ":type filename: str\n"
291 ":param x: X position where the sound is playing from.\n"
292 ":type x: int\n"
293 ":param y: Y position where the sound is playing from.\n"
294 ":type y: int\n"
295 ":param type: Sound type to play, one of the CMD_SOUND_xxx constants, eg, "
296 ":attr:`~Atrinik.CMD_SOUND_BACKGROUND`.\n"
297 ":type type: int\n"
298 ":param loop: How many times to loop the sound, -1 to loop infinitely.\n"
299 ":type loop: int\n"
300 ":param volume: Volume adjustment.\n"
301 ":type volume: int";
302 
307 static PyObject *Atrinik_Map_PlaySound(Atrinik_Map *self, PyObject *args,
308  PyObject *keywds)
309 {
310  static char *kwlist[] = {
311  "filename", "x", "y", "type", "loop", "volume", NULL
312  };
313  const char *filename;
314  int x, y, type = CMD_SOUND_EFFECT, loop = 0, volume = 0;
315 
316  if (!PyArg_ParseTupleAndKeywords(args, keywds, "sii|iii", kwlist,
317  &filename, &x, &y, &type, &loop, &volume)) {
318  return NULL;
319  }
320 
321  hooks->play_sound_map(self->map, type, filename, x, y, loop, volume);
322 
323  Py_INCREF(Py_None);
324  return Py_None;
325 }
326 
328 static const char doc_Atrinik_Map_DrawInfo[] =
329 ".. method:: DrawInfo(x, y, message, color=Atrinik.COLOR_BLUE, "
330 "type=Atrinik.CHAT_TYPE_GAME, name=None, distance=Atrinik.MAP_INFO_NORMAL).\n\n"
331 "Send a message to all players on the map.\n\n"
332 ":param x: X position on the map.\n"
333 ":type x: int\n"
334 ":param y: Y position on the map.\n"
335 ":type y: int\n"
336 ":param message: The message to send.\n"
337 ":type message: str\n"
338 ":param color: Color to use for the message. Can be one of the COLOR_xxx "
339 "constants (eg, :attr:`~Atrinik.COLOR_RED`) or a regular HTML color notation "
340 "(eg, '00ff00')\n"
341 ":type color: str\n"
342 ":param type: One of the CHAT_TYPE_xxx constants, eg, :attr:"
343 "`~Atrinik.CHAT_TYPE_CHAT`.\n"
344 ":type type: int\n"
345 ":param name: Player name that is the source of this message, if applicable.\n"
346 ":type name: str or None\n"
347 ":param distance: Maximum distance for players to be away from *x*, *y* to "
348 "hear the message.\n"
349 ":type distance: int";
350 
355 static PyObject *Atrinik_Map_DrawInfo(Atrinik_Map *self, PyObject *args,
356  PyObject *keywds)
357 {
358  static char *kwlist[] = {
359  "x", "y", "message", "color", "type", "name", "distance", NULL
360  };
361  int x, y, distance;
362  const char *message, *color, *name;
363  uint8_t type;
364 
365  color = COLOR_BLUE;
366  type = CHAT_TYPE_GAME;
367  name = NULL;
368  distance = MAP_INFO_NORMAL;
369 
370  if (!PyArg_ParseTupleAndKeywords(args, keywds, "iis|sbzi", kwlist, &x, &y,
371  &message, &color, &type, &name, &distance)) {
372  return NULL;
373  }
374 
375  hooks->draw_info_map(type, name, color, self->map, x, y, distance, NULL,
376  NULL, message);
377 
378  Py_INCREF(Py_None);
379  return Py_None;
380 }
381 
383 static const char doc_Atrinik_Map_CreateObject[] =
384 ".. method:: CreateObject(archname, x, y).\n\n"
385 "Create an object on the map.\n\n"
386 ":param archname: Arch name of the object to create.\n"
387 ":type archname: str\n"
388 ":param x: X position on the map.\n"
389 ":type x: int\n"
390 ":param y: Y position on the map.\n"
391 ":type y: int\n"
392 ":returns: The created object.\n"
393 ":rtype: :class:`Atrinik.Object.Object`\n"
394 ":raises Atrinik.AtrinikError: If *archname* is not a valid archetype.";
395 
400 static PyObject *Atrinik_Map_CreateObject(Atrinik_Map *self, PyObject *args)
401 {
402  const char *archname;
403  int x, y;
404 
405  if (!PyArg_ParseTuple(args, "sii", &archname, &x, &y)) {
406  return NULL;
407  }
408 
409  archetype_t *arch = hooks->arch_find(archname);
410  if (arch == NULL) {
411  RAISE("Invalid archetype.");
412  return NULL;
413  }
414 
415  object *newobj = hooks->arch_to_object(arch);
416  newobj->x = x;
417  newobj->y = y;
418  newobj = hooks->object_insert_map(newobj, self->map, NULL, 0);
419 
420  return wrap_object(newobj);
421 }
422 
424 static const char doc_Atrinik_Map_CountPlayers[] =
425 ".. method:: CountPlayers().\n\n"
426 "Count number of players on map.\n\n"
427 ":returns: The number of players on the map.\n"
428 ":rtype: int";
429 
434 static PyObject *Atrinik_Map_CountPlayers(Atrinik_Map *self)
435 {
436  return Py_BuildValue("i", hooks->players_on_map(self->map));
437 }
438 
440 static const char doc_Atrinik_Map_GetPlayers[] =
441 ".. method:: GetPlayers().\n\n"
442 "Get all the players on a specified map.\n\n"
443 ":returns: List containing player objects on the map.\n"
444 ":rtype: :attr:`list` of :class:`Atrinik.Object.Object`";
445 
450 static PyObject *Atrinik_Map_GetPlayers(Atrinik_Map *self)
451 {
452  PyObject *list = PyList_New(0);
453 
454  for (object *tmp = self->map->player_first; tmp != NULL;
455  tmp = CONTR(tmp)->map_above) {
456  PyList_Append(list, wrap_object(tmp));
457  }
458 
459  return list;
460 }
461 
463 static const char doc_Atrinik_Map_Insert[] =
464 ".. method:: Insert(obj, x, y).\n\n"
465 "Insert the specified object on map, removing it first if necessary.\n\n"
466 ":param obj: Object to insert.\n"
467 ":type obj: :class:`Atrinik.Object.Object`\n"
468 ":param x: X position on the map.\n"
469 ":type x: int\n"
470 ":param y: Y position on the map.\n"
471 ":type y: int\n"
472 ":returns: The inserted object. Can be None on failure, or different from "
473 "*obj* in case of merging.\n"
474 ":rtype: :class:`Atrinik.Object.Object` or None";
475 
480 static PyObject *Atrinik_Map_Insert(Atrinik_Map *self, PyObject *args)
481 {
483  int16_t x, y;
484 
485  if (!PyArg_ParseTuple(args, "O!hh", &Atrinik_ObjectType, &obj, &x, &y)) {
486  return NULL;
487  }
488 
489  OBJEXISTCHECK(obj);
490 
491  if (!QUERY_FLAG(obj->obj, FLAG_REMOVED)) {
492  hooks->object_remove(obj->obj, 0);
493  }
494 
495  obj->obj->x = x;
496  obj->obj->y = y;
497  return wrap_object(hooks->object_insert_map(obj->obj, self->map, NULL, 0));
498 }
499 
501 static const char doc_Atrinik_Map_Wall[] =
502 ".. method:: Wall(x, y).\n\n"
503 "Checks if there's a wall on the specified square.\n\n"
504 ":param x: X position on the map.\n"
505 ":type x: int\n"
506 ":param y: Y position on the map.\n"
507 ":type y: int\n"
508 ":returns: A combination of P_xxx, eg, :attr:`~Atrinik.P_NO_PASS`. Zero means "
509 "the square is not blocked by anything.\n"
510 ":rtype: int";
511 
516 static PyObject *Atrinik_Map_Wall(Atrinik_Map *self, PyObject *args)
517 {
518  int16_t x, y;
519 
520  if (!PyArg_ParseTuple(args, "hh", &x, &y)) {
521  return NULL;
522  }
523 
524  return Py_BuildValue("i", hooks->wall(self->map, x, y));
525 }
526 
528 static const char doc_Atrinik_Map_Blocked[] =
529 ".. method:: Blocked(obj, x, y, terrain).\n\n"
530 "Check if specified square is blocked for *obj*.\n\nIf you simply need to "
531 "check if there's a wall on a square, you should use :meth:"
532 "`~Atrinik.Map.Map.Wall` instead.\n\n"
533 ":param obj: Object to check.\n"
534 ":type obj: :class:`Atrinik.Object.Object`\n"
535 ":param x: X position on the map.\n"
536 ":type x: int\n"
537 ":param y: Y position on the map.\n"
538 ":type y: int\n"
539 ":param terrain: Terrain the object is allowed to go to. One (or a "
540 "combination) of the TERRAIN_xxx constants, eg, :attr:"
541 "`~Atrinik.TERRAIN_AIRBREATH`. The object's :attr:"
542 "`~Atrinik.Object.Object.terrain_flag` can be used for this purpose.\n"
543 ":type terrain: int\n"
544 ":returns: A combination of P_xxx, eg, :attr:`~Atrinik.P_NO_PASS`. Zero means "
545 "the square is not blocked for the object.\n"
546 ":rtype: int\n"
547 ":raises: Atrinik.AtrinikError: If there was a problem resolving the specified "
548 "X/Y coordinates to a tiled map (if they were outside the map).";
549 
554 static PyObject *Atrinik_Map_Blocked(Atrinik_Map *self, PyObject *args)
555 {
557  int x, y, terrain;
558 
559  if (!PyArg_ParseTuple(args, "O!iii", &Atrinik_ObjectType, &obj, &x, &y,
560  &terrain)) {
561  return NULL;
562  }
563 
564  OBJEXISTCHECK(obj);
565 
566  mapstruct *m = hooks->get_map_from_coord(self->map, &x, &y);
567  if (m == NULL) {
568  RAISE("Unable to get map using get_map_from_coord().");
569  }
570 
571  return Py_BuildValue("i", hooks->blocked(obj->obj, m, x, y, terrain));
572 }
573 
575 static const char doc_Atrinik_Map_FreeSpot[] =
576 ".. method:: FreeSpot(obj, x, y, start, stop).\n\n"
577 "Find first free spot around map at *x*, *y*. The resulting value can be used "
578 "as an index to both free spot arrays; :attr:`Atrinik.freearr_x` and "
579 ":attr:`Atrinik.freearr_y`.\n\n"
580 ":param obj: Involved object - will be used to find the spot this object could "
581 "move onto.\n"
582 ":type obj: :class:`Atrinik.Object.Object`\n"
583 ":param x: X position on the map.\n"
584 ":type x: int\n"
585 ":param y: Y position on the map.\n"
586 ":type y: int\n"
587 ":param start: Start in the free spot arrays; 0 will also check the tile at "
588 "*x*, *y*, whereas 1 will start searching only around *x*, *y*.\n"
589 ":type start: int\n"
590 ":param stop: Where to stop in the free spot arrays; one of the SIZEOFFREEx "
591 "constants, eg, :attr:`~Atrinik.SIZEOFFREE1`."
592 ":type stop: int\n"
593 ":returns: -1 on error, index to the free spot arrays otherwise.\n"
594 ":rtype: int\n"
595 ":raises: ValueError: If either *start* or *stop* are not in a valid range.";
596 
601 static PyObject *Atrinik_Map_FreeSpot(Atrinik_Map *self, PyObject *args)
602 {
604  int x, y, start, stop;
605 
606  if (!PyArg_ParseTuple(args, "O!iiii", &Atrinik_ObjectType, &obj, &x, &y,
607  &start, &stop)) {
608  return NULL;
609  }
610 
611  OBJEXISTCHECK(obj);
612 
613  if (start < 0 || stop < 0) {
614  PyErr_SetString(PyExc_ValueError,
615  "'start' and 'stop' cannot be negative.");
616  return NULL;
617  }
618 
619  if (stop >= SIZEOFFREE) {
620  PyErr_SetString(PyExc_ValueError,
621  "'stop' cannot be higher than or equal to SIZEOFFREE.");
622  return NULL;
623  }
624 
625  mapstruct *m = hooks->get_map_from_coord(self->map, &x, &y);
626  if (m == NULL) {
627  return Py_BuildValue("i", -1);
628  }
629 
630  return Py_BuildValue("i",
631  hooks->map_free_spot(m,
632  x,
633  y,
634  start,
635  stop,
636  obj->obj->arch,
637  obj->obj));
638 }
639 
641 static const char doc_Atrinik_Map_GetDarkness[] =
642 ".. method:: GetDarkness(x, y).\n\n"
643 "Gets the darkness value of the specified square.\n\n"
644 ":param x: X position on the map.\n"
645 ":type x: int\n"
646 ":param y: Y position on the map.\n"
647 ":type y: int\n"
648 ":returns: The darkness value.\n"
649 ":rtype: int\n"
650 ":raises: Atrinik.AtrinikError: If there was a problem resolving the specified "
651 "X/Y coordinates to a tiled map (if they were outside the map).";
652 
657 static PyObject *Atrinik_Map_GetDarkness(Atrinik_Map *self, PyObject *args)
658 {
659  int x, y;
660 
661  if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
662  return NULL;
663  }
664 
665  mapstruct *m = hooks->get_map_from_coord(self->map, &x, &y);
666  if (m == NULL) {
667  RAISE("Unable to get map using get_map_from_coord().");
668  }
669 
670  return Py_BuildValue("i", hooks->map_get_darkness(m, x, y, NULL));
671 }
672 
674 static const char doc_Atrinik_Map_GetPath[] =
675 ".. method:: GetPath(path=None, unique=None, name=None).\n\n"
676 "Construct a path based on the path of the map, with *path* appended.\n\n"
677 ":param path: Path to append. If None, will append the filename of the map "
678 "instead.\n"
679 ":type path: str or None\n"
680 ":param unique: Whether to construct a unique path. If None, unique flag of "
681 "the map will be used.\n"
682 ":type unique: bool or None\n"
683 ":param name: If the map is not unique and *unique* is True, this is required "
684 "to determine which player the unique map path should belong to.\n"
685 ":type name: str or None\n"
686 ":returns: The created path.\n"
687 ":rtype: str";
688 
693 static PyObject *Atrinik_Map_GetPath(Atrinik_Map *self, PyObject *args,
694  PyObject *keywds)
695 {
696  static char *kwlist[] = {"path", "unique", "name", NULL};
697  const char *path, *name;
698  int unique;
699 
700  path = NULL;
701  unique = MAP_UNIQUE(self->map) != 0;
702  name = NULL;
703 
704  if (!PyArg_ParseTupleAndKeywords(args, keywds, "|zis", kwlist, &path,
705  &unique, &name)) {
706  return NULL;
707  }
708 
709  char *cp = hooks->map_get_path(self->map, path, unique, name);
710  PyObject *ret = Py_BuildValue("s", cp);
711  efree(cp);
712 
713  return ret;
714 }
715 
717 static const char doc_Atrinik_Map_LocateBeacon[] =
718 ".. method:: LocateBeacon(name).\n\n"
719 "Locate a beacon.\n\n"
720 ":param name: The beacon name to find.\n"
721 ":type name: str\n"
722 ":returns: The beacon object if found, None otherwise.\n"
723 ":rtype: :class:`Atrinik.Object.Object` or None";
724 
729 static PyObject *Atrinik_Map_LocateBeacon(Atrinik_Map *self, PyObject *args)
730 {
731  const char *name;
732 
733  if (!PyArg_ParseTuple(args, "s", &name)) {
734  return NULL;
735  }
736 
737  shstr *beacon_name;
738  if (MAP_UNIQUE(self->map)) {
739  /* The map is unique, so we need to do some work to create a unique
740  * beacon name from the map's unique path. */
741  char *filedir = hooks->path_dirname(self->map->path);
742  char *pl_name = hooks->path_basename(filedir);
743  char *joined = hooks->string_join("-", "/", pl_name, name, NULL);
744 
745  beacon_name = hooks->add_string(joined);
746 
747  efree(joined);
748  efree(pl_name);
749  efree(filedir);
750  } else {
751  beacon_name = hooks->add_string(name);
752  }
753 
754  object *obj = hooks->beacon_locate(beacon_name);
755  hooks->free_string_shared(beacon_name);
756 
757  return wrap_object(obj);
758 }
759 
761 static const char doc_Atrinik_Map_Redraw[] =
762 ".. method:: Redraw(x, y, layer=-1, sub_layer=-1).\n\n"
763 "Redraw the specified tile for all players.\n\n"
764 ":param x: X position on the map.\n"
765 ":type x: int\n"
766 ":param y: Y position on the map.\n"
767 ":type y: int\n"
768 ":param layer: Layer to redraw. If -1, will redraw all layers.\n"
769 ":type layer: int\n"
770 ":param sub_layer: Sub-layer to redraw. If -1, will redraw all sub-layers.\n"
771 ":type sub_layer: int\n"
772 ":raises Atrinik.AtrinikError: If either coordinates are not within the map.\n"
773 ":raises Atrinik.AtrinikError: If *layer* is not within a valid range.\n"
774 ":raises Atrinik.AtrinikError: If *sub_layer* is not within a valid range.";
775 
780 static PyObject *Atrinik_Map_Redraw(Atrinik_Map *self, PyObject *args)
781 {
782  int x, y, layer, sub_layer;
783 
784  layer = -1;
785  sub_layer = -1;
786 
787  if (!PyArg_ParseTuple(args, "ii|ii", &x, &y, &layer, &sub_layer)) {
788  return NULL;
789  }
790 
791  if (x < 0 || x >= self->map->width) {
792  RAISE("Invalid X coordinate.");
793  }
794 
795  if (y < 0 || y >= self->map->height) {
796  RAISE("Invalid X coordinate.");
797  }
798 
799  if (layer < -1 || layer > NUM_LAYERS) {
800  RAISE("Invalid layer.");
801  }
802 
803  if (sub_layer < -1 || sub_layer >= NUM_SUB_LAYERS) {
804  RAISE("Invalid sub-layer.");
805  }
806 
807  hooks->map_redraw(self->map, x, y, layer, sub_layer);
808 
809  Py_INCREF(Py_None);
810  return Py_None;
811 }
812 
814 static const char doc_Atrinik_Map_Save[] =
815 ".. method:: Save().\n\n"
816 "Saves the specified map.\n\n";
817 
822 static PyObject *Atrinik_Map_Save(Atrinik_Map *self)
823 {
824  hooks->new_save_map(self->map, 0);
825 
826  Py_INCREF(Py_None);
827  return Py_None;
828 }
829 
831 static PyMethodDef MapMethods[] = {
832  {"Objects", (PyCFunction) Atrinik_Map_Objects, METH_VARARGS,
834  {"ObjectsReversed", (PyCFunction) Atrinik_Map_ObjectsReversed, METH_VARARGS,
836  {"GetLayer", (PyCFunction) Atrinik_Map_GetLayer, METH_VARARGS,
838  {"GetMapFromCoord", (PyCFunction) Atrinik_Map_GetMapFromCoord, METH_VARARGS,
840  {"PlaySound", (PyCFunction) Atrinik_Map_PlaySound,
841  METH_VARARGS | METH_KEYWORDS, doc_Atrinik_Map_PlaySound},
842  {"DrawInfo", (PyCFunction) Atrinik_Map_DrawInfo,
843  METH_VARARGS | METH_KEYWORDS, doc_Atrinik_Map_DrawInfo},
844  {"CreateObject", (PyCFunction) Atrinik_Map_CreateObject, METH_VARARGS,
846  {"CountPlayers", (PyCFunction) Atrinik_Map_CountPlayers, METH_NOARGS,
848  {"GetPlayers", (PyCFunction) Atrinik_Map_GetPlayers, METH_NOARGS,
850  {"Insert", (PyCFunction) Atrinik_Map_Insert, METH_VARARGS,
852  {"Wall", (PyCFunction) Atrinik_Map_Wall, METH_VARARGS,
854  {"Blocked", (PyCFunction) Atrinik_Map_Blocked, METH_VARARGS,
856  {"FreeSpot", (PyCFunction) Atrinik_Map_FreeSpot, METH_VARARGS,
858  {"GetDarkness", (PyCFunction) Atrinik_Map_GetDarkness, METH_VARARGS,
860  {"GetPath", (PyCFunction) Atrinik_Map_GetPath, METH_VARARGS | METH_KEYWORDS,
862  {"LocateBeacon", (PyCFunction) Atrinik_Map_LocateBeacon, METH_VARARGS,
864  {"Redraw", (PyCFunction) Atrinik_Map_Redraw, METH_VARARGS,
866  {"Save", (PyCFunction) Atrinik_Map_Save, METH_NOARGS,
868 
869  {NULL, NULL, 0, NULL}
870 };
871 
881 static PyObject *get_attribute(Atrinik_Map *map, void *context)
882 {
883  return generic_field_getter(context, map->map);
884 }
885 
897 static int set_attribute(Atrinik_Map *map, PyObject *value, void *context)
898 {
899  if (generic_field_setter(context, map->map, value) == -1) {
900  return -1;
901  }
902 
903  if (((fields_struct *) context)->offset == offsetof(mapstruct, darkness)) {
904  hooks->set_map_darkness(map->map, map->map->darkness);
905  }
906 
907  return 0;
908 }
909 
920 static PyObject *Map_GetFlag(Atrinik_Map *map, void *context)
921 {
922  size_t flagno = (size_t) context;
923 
924  /* Should not happen. */
925  if (flagno >= NUM_MAPFLAGS) {
926  PyErr_SetString(PyExc_OverflowError, "Invalid flag ID.");
927  return NULL;
928  }
929 
930  return Py_BuildBoolean(map->map->map_flags & (1 << flagno));
931 }
932 
944 static int Map_SetFlag(Atrinik_Map *map, PyObject *val, void *context)
945 {
946  size_t flagno = (size_t) context;
947 
948  /* Should not happen. */
949  if (flagno >= NUM_MAPFLAGS) {
950  PyErr_SetString(PyExc_OverflowError, "Invalid flag ID.");
951  return -1;
952  }
953 
954  if (val == Py_True) {
955  map->map->map_flags |= (1U << flagno);
956  } else if (val == Py_False) {
957  map->map->map_flags &= ~(1U << flagno);
958  } else {
959  PyErr_SetString(PyExc_TypeError,
960  "Flag value must be either True or False.");
961  return -1;
962  }
963 
964  return 0;
965 }
966 
978 static PyObject *Atrinik_Map_new(PyTypeObject *type, PyObject *args,
979  PyObject *kwds)
980 {
981  Atrinik_Map *self = (Atrinik_Map *) type->tp_alloc(type, 0);
982  if (self != NULL) {
983  self->map = NULL;
984  }
985 
986  return (PyObject *) self;
987 }
988 
995 {
996  self->map = NULL;
997 #ifndef IS_PY_LEGACY
998  Py_TYPE(self)->tp_free((PyObject *) self);
999 #else
1000  self->ob_type->tp_free((PyObject *) self);
1001 #endif
1002 }
1003 
1011 static PyObject *Atrinik_Map_str(Atrinik_Map *self)
1012 {
1013  char buf[HUGE_BUF];
1014  snprintf(VS(buf), "[%s \"%s\"]", self->map->path, self->map->name);
1015  return Py_BuildValue("s", buf);
1016 }
1017 
1018 static int Atrinik_Map_InternalCompare(Atrinik_Map *left, Atrinik_Map *right)
1019 {
1020  return left->map < right->map ? -1 : (left->map == right->map ? 0 : 1);
1021 }
1022 
1023 static PyObject *Atrinik_Map_RichCompare(Atrinik_Map *left, Atrinik_Map *right,
1024  int op)
1025 {
1026  if (left == NULL || right == NULL ||
1027  !PyObject_TypeCheck((PyObject *) left, &Atrinik_MapType) ||
1028  !PyObject_TypeCheck((PyObject *) right, &Atrinik_MapType)) {
1029  Py_INCREF(Py_NotImplemented);
1030  return Py_NotImplemented;
1031  }
1032 
1033  return generic_rich_compare(op, Atrinik_Map_InternalCompare(left, right));
1034 }
1035 
1037 static PyGetSetDef getseters[NUM_FIELDS + NUM_MAPFLAGS + 1];
1038 
1040 PyTypeObject Atrinik_MapType = {
1041 #ifdef IS_PY3K
1042  PyVarObject_HEAD_INIT(NULL, 0)
1043 #else
1044  PyObject_HEAD_INIT(NULL)
1045  0,
1046 #endif
1047  "Atrinik.Map",
1048  sizeof(Atrinik_Map),
1049  0,
1050  (destructor) Atrinik_Map_dealloc,
1051  NULL, NULL, NULL,
1052 #ifdef IS_PY3K
1053  NULL,
1054 #else
1055  (cmpfunc) Atrinik_Map_InternalCompare,
1056 #endif
1057  0, 0, 0, 0, 0, 0,
1058  (reprfunc) Atrinik_Map_str,
1059  0, 0, 0,
1060  Py_TPFLAGS_DEFAULT,
1061  "Atrinik maps",
1062  NULL, NULL,
1063  (richcmpfunc) Atrinik_Map_RichCompare,
1064  0, 0, 0,
1065  MapMethods,
1066  0,
1067  getseters,
1068  0, 0, 0, 0, 0, 0, 0,
1070  0, 0, 0, 0, 0, 0, 0, 0
1071 #ifndef IS_PY_LEGACY
1072  , 0
1073 #endif
1074 #ifdef Py_TPFLAGS_HAVE_FINALIZE
1075  , NULL
1076 #endif
1077 };
1078 
1086 int Atrinik_Map_init(PyObject *module)
1087 {
1088  size_t i, flagno;
1089 
1090  /* Field getters */
1091  for (i = 0; i < NUM_FIELDS; i++) {
1092  PyGetSetDef *def = &getseters[i];
1093 
1094  def->name = fields[i].name;
1095  def->get = (getter) get_attribute;
1096  def->set = (setter) set_attribute;
1097  def->doc = fields[i].doc;
1098  def->closure = &fields[i];
1099  }
1100 
1101  /* Flag getters */
1102  for (flagno = 0; flagno < NUM_MAPFLAGS; flagno++) {
1103  PyGetSetDef *def;
1104 
1105  if (mapflag_names[flagno] == NULL) {
1106  continue;
1107  }
1108 
1109  def = &getseters[i++];
1110 
1111  def->name = mapflag_names[flagno];
1112  def->get = (getter) Map_GetFlag;
1113  def->set = (setter) Map_SetFlag;
1114  def->doc = mapflag_docs[flagno];
1115  def->closure = (void *) flagno;
1116  }
1117 
1118  getseters[i].name = NULL;
1119 
1120  Atrinik_MapType.tp_new = PyType_GenericNew;
1121 
1122  if (PyType_Ready(&Atrinik_MapType) < 0) {
1123  return 0;
1124  }
1125 
1126  Py_INCREF(&Atrinik_MapType);
1127  PyModule_AddObject(module, "Map", (PyObject *) &Atrinik_MapType);
1128 
1129  return 1;
1130 }
1131 
1139 PyObject *wrap_map(mapstruct *what)
1140 {
1141  /* Return None if no map was to be wrapped. */
1142  if (what == NULL) {
1143  Py_INCREF(Py_None);
1144  return Py_None;
1145  }
1146 
1147  Atrinik_Map *wrapper = PyObject_NEW(Atrinik_Map, &Atrinik_MapType);
1148  if (wrapper != NULL) {
1149  wrapper->map = what;
1150  }
1151 
1152  return (PyObject *) wrapper;
1153 }
static char * mapflag_names[]
Definition: atrinik_map.c:90
PyTypeObject Atrinik_ObjectType
Definition: object.h:94
static const char doc_Atrinik_Map_Insert[]
Definition: atrinik_map.c:463
static const char doc_Atrinik_Map_GetPath[]
Definition: atrinik_map.c:674
static void Atrinik_Map_dealloc(Atrinik_Map *self)
Definition: atrinik_map.c:994
static PyObject * Atrinik_Map_ObjectsReversed(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:172
static const char doc_Atrinik_Map_Redraw[]
Definition: atrinik_map.c:761
struct plugin_hooklist * hooks
Definition: plugin_arena.c:160
int darkness
Definition: map.h:638
uint8_t type
One of operation types.
Definition: sound_ambient.c:45
PyObject * wrap_object_iterator(object *what)
#define Py_BuildBoolean(val)
static PyMethodDef MapMethods[]
Definition: atrinik_map.c:831
static const char doc_Atrinik_Map_Blocked[]
Definition: atrinik_map.c:528
static const char doc_Atrinik_Map_ObjectsReversed[]
Definition: atrinik_map.c:155
#define RAISE(msg)
int generic_field_setter(fields_struct *field, void *ptr, PyObject *value)
static int set_attribute(Atrinik_Map *map, PyObject *value, void *context)
Definition: atrinik_map.c:897
PyObject * generic_rich_compare(int op, int result)
static PyObject * Atrinik_Map_PlaySound(Atrinik_Map *self, PyObject *args, PyObject *keywds)
Definition: atrinik_map.c:307
static PyObject * Atrinik_Map_LocateBeacon(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:729
static const char doc_Atrinik_Map_GetDarkness[]
Definition: atrinik_map.c:641
PyObject * wrap_object(object *what)
#define QUERY_FLAG(xyz, p)
Definition: define.h:761
struct archetype * arch
Definition: object.h:225
static const char doc_Atrinik_Map_GetMapFromCoord[]
Definition: atrinik_map.c:252
#define IS_PY_LEGACY
Definition: plugin_python.h:55
static PyObject * Atrinik_Map_CountPlayers(Atrinik_Map *self)
Definition: atrinik_map.c:434
static PyObject * get_attribute(Atrinik_Map *map, void *context)
Definition: atrinik_map.c:881
int16_t y
Definition: object.h:276
static const char doc_Atrinik_Map_DrawInfo[]
Definition: atrinik_map.c:328
static PyObject * Atrinik_Map_CreateObject(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:400
static PyGetSetDef getseters[NUM_FIELDS+NUM_MAPFLAGS+1]
Definition: atrinik_map.c:1037
Definition: arch.h:40
static PyObject * Atrinik_Map_Blocked(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:554
#define MAP_INFO_NORMAL
Definition: global.h:83
static PyObject * Atrinik_Map_DrawInfo(Atrinik_Map *self, PyObject *args, PyObject *keywds)
Definition: atrinik_map.c:355
PyObject * wrap_map(mapstruct *what)
Definition: atrinik_map.c:1139
static PyObject * Map_GetFlag(Atrinik_Map *map, void *context)
Definition: atrinik_map.c:920
uint32_t map_flags
Definition: map.h:605
struct sound_ambient_match * next
Next match rule in a linked list.
Definition: sound_ambient.c:39
static const char doc_Atrinik_Map_GetPlayers[]
Definition: atrinik_map.c:440
struct mapdef * map
Definition: object.h:139
#define FIELDFLAG_READONLY
#define NUM_FIELDS
static const char doc_Atrinik_Map_Wall[]
Definition: atrinik_map.c:501
static const char doc_Atrinik_Map_Objects[]
Definition: atrinik_map.c:119
static PyObject * Atrinik_Map_FreeSpot(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:601
static PyObject * Atrinik_Map_Save(Atrinik_Map *self)
Definition: atrinik_map.c:822
static const char doc_Atrinik_Map_Save[]
Definition: atrinik_map.c:814
static PyObject * Atrinik_Map_GetMapFromCoord(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:267
#define NUM_LAYERS
Definition: map.h:67
#define FOR_MAP_LAYER_END
Definition: define.h:1657
static const char doc_Atrinik_Map_GetLayer[]
Definition: atrinik_map.c:191
static PyObject * Atrinik_Map_str(Atrinik_Map *self)
Definition: atrinik_map.c:1011
static PyObject * Atrinik_Map_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Definition: atrinik_map.c:978
static PyObject * Atrinik_Map_GetPlayers(Atrinik_Map *self)
Definition: atrinik_map.c:450
int16_t x
Definition: object.h:273
#define NUM_SUB_LAYERS
Definition: map.h:71
static PyObject * Atrinik_Map_Insert(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:480
#define FOR_MAP_LAYER_BEGIN(_m, _x, _y, _layer, _sub_layer, _obj)
Definition: define.h:1630
#define FLAG_REMOVED
Definition: define.h:930
PyObject_HEAD mapstruct * map
#define SIZEOFFREE
Definition: define.h:660
static int Map_SetFlag(Atrinik_Map *map, PyObject *val, void *context)
Definition: atrinik_map.c:944
PyObject * generic_field_getter(fields_struct *field, void *ptr)
static char * mapflag_docs[]
Definition: atrinik_map.c:98
static PyObject * Atrinik_Map_Wall(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:516
static PyObject * Atrinik_Map_Redraw(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:780
static PyObject * Atrinik_Map_Objects(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:136
static const char doc_Atrinik_Map_PlaySound[]
Definition: atrinik_map.c:285
static const char doc_Atrinik_Map_FreeSpot[]
Definition: atrinik_map.c:575
static fields_struct fields[]
Definition: atrinik_map.c:39
#define MAP_UNIQUE(m)
Definition: map.h:96
static const char doc_Atrinik_Map_LocateBeacon[]
Definition: atrinik_map.c:717
static const char doc_Atrinik_Map_CreateObject[]
Definition: atrinik_map.c:383
static PyObject * Atrinik_Map_GetDarkness(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:657
static const char doc_Atrinik_Map_CountPlayers[]
Definition: atrinik_map.c:424
int Atrinik_Map_init(PyObject *module)
Definition: atrinik_map.c:1086
static PyObject * Atrinik_Map_GetPath(Atrinik_Map *self, PyObject *args, PyObject *keywds)
Definition: atrinik_map.c:693
#define NUM_MAPFLAGS
Definition: atrinik_map.c:116
PyTypeObject Atrinik_MapType
Definition: atrinik_map.c:1040
Definition: map.h:536
static PyObject * Atrinik_Map_GetLayer(Atrinik_Map *self, PyObject *args)
Definition: atrinik_map.c:220
PyObject_HEAD object * obj