ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/rune.C
Revision: 1.24
Committed: Thu May 17 00:33:29 2007 UTC (17 years ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: rel-2_1
Changes since 1.23: +1 -1 lines
Log Message:
- do not require an item for the use magic item skill.
  thats because it is often used for "misc" objects such as scrolls,
  which do not get applied as ranged weapons. not requiring an item
  is not likely to be a big deal anyways.

File Contents

# Content
1 /*
2 * CrossFire, A Multiplayer game
3 *
4 * Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team
5 * Copyright (C) 2003 Mark Wedel & Crossfire Development Team
6 * Copyright (C) 1992 Frank Tore Johansen
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 authors can be reached via e-mail at <crossfire@schmorp.de>
23 */
24
25 #include <global.h>
26 #include <sproto.h>
27 #include <spells.h>
28
29 #ifndef sqr
30 # define sqr(x) ((x)*(x))
31 #endif
32
33 /* peterm:
34 * write_rune:
35 * op: rune writer
36 * skop: skill object used for casting this rune
37 * dir: orientation of rune, direction rune's contained spell will
38 * be cast in, if applicable
39 * inspell: spell object to put into the rune, can be null if doing
40 * a marking rune.
41 * level: level of casting of the rune
42 * runename: name of the rune or message displayed by the rune for
43 * a rune of marking
44 */
45
46 int
47 write_rune (object *op, object *caster, object *spell, int dir, const char *runename)
48 {
49 object *tmp, *rune_spell, *rune;
50 char buf[MAX_BUF];
51 maptile *m;
52 sint16 nx, ny;
53
54 if (!dir)
55 dir = 1;
56
57 nx = op->x + freearr_x[dir];
58 ny = op->y + freearr_y[dir];
59 m = op->map;
60
61 if (get_map_flags (m, &m, nx, ny, &nx, &ny) & (P_OUT_OF_MAP | P_SAFE | P_NO_MAGIC | P_NO_CLERIC))
62 {
63 new_draw_info (NDI_UNIQUE, 0, op, "Can't make a rune there!");
64 return 0;
65 }
66
67 for (tmp = m->at (nx, ny).bot; tmp; tmp = tmp->above)
68 if (tmp->type == RUNE)
69 break;
70
71 if (tmp)
72 {
73 new_draw_info (NDI_UNIQUE, 0, op, "You can't write a rune on top of another rune.");
74 return 0;
75 }
76
77 if (spell->other_arch)
78 rune_spell = arch_to_object (spell->other_arch);
79 else
80 {
81 /* Player specified spell. The player has to know the spell, so
82 * lets just look through the players inventory see if they know it
83 * use the item_matched_string for our typical matching method.
84 */
85 int bestmatch = 0, ms;
86
87 if (!runename || *runename == 0)
88 {
89 new_draw_info (NDI_UNIQUE, 0, op, "Write a rune of what?");
90 return 0;
91 }
92
93 rune_spell = NULL;
94 for (tmp = op->inv; tmp; tmp = tmp->below)
95 if (tmp->type == SPELL)
96 {
97 ms = item_matched_string (op, tmp, runename);
98 if (ms > bestmatch)
99 {
100 bestmatch = ms;
101 rune_spell = tmp;
102 }
103 }
104
105 if (!rune_spell)
106 {
107 new_draw_info_format (NDI_UNIQUE, 0, op, "You don't know any spell named %s", runename);
108 return 0;
109 }
110
111 if (rune_spell->skill != spell->skill)
112 {
113 new_draw_info_format (NDI_UNIQUE, 0, op, "You can't cast %s with %s", &rune_spell->name, &spell->name);
114 return 0;
115 }
116
117 if (caster->path_denied & spell->path_attuned && !caster->flag [FLAG_WIZCAST])
118 {
119 new_draw_info_format (NDI_UNIQUE, 0, op, "%s belongs to a spell path denied to you.", &rune_spell->name);
120 return 0;
121 }
122
123 if (caster_level (caster, rune_spell) < rune_spell->level && !caster->flag [FLAG_WIZCAST])
124 {
125 new_draw_info_format (NDI_UNIQUE, 0, op, "%s is beyond your ability to cast!", &rune_spell->name);
126 return 0;
127 }
128
129 if (SP_level_spellpoint_cost (caster, rune_spell, SPELL_MANA) > op->stats.sp)
130 {
131 new_draw_info (NDI_UNIQUE, 0, op, "You don't have enough mana.");
132 return 0;
133 }
134
135 if (SP_level_spellpoint_cost (caster, rune_spell, SPELL_GRACE) > op->stats.grace)
136 {
137 new_draw_info (NDI_UNIQUE, 0, op, "You don't have enough grace.");
138 return 0;
139 }
140
141 op->stats.grace -= SP_level_spellpoint_cost (caster, rune_spell, SPELL_GRACE);
142 op->stats.sp -= SP_level_spellpoint_cost (caster, rune_spell, SPELL_MANA);
143 }
144
145 /* already proper rune. Note this should only be the case if other_arch was set */
146 if (rune_spell->type == RUNE)
147 rune = rune_spell;
148 else
149 {
150 rune = get_archetype (GENERIC_RUNE);
151 sprintf (buf, "You set off a rune of %s\n", &rune_spell->name);
152 rune->msg = buf;
153 rune->insert (rune_spell->clone ());
154
155 if (spell->face != blank_face)
156 rune->face = spell->face;
157 }
158
159 rune->level = caster_level (caster, spell);
160 rune->stats.Cha = rune->level / 2; /* the invisibility parameter */
161 rune->direction = dir; /* where any spell will go upon detonation */
162 rune->set_owner (op); /* runes without need no owner */
163 set_spell_skill (op, caster, spell, rune);
164
165 m->insert (rune, nx, ny, op);
166 return 1;
167 }
168
169 /* move_rune: peterm
170 comments on runes:
171 rune->level : level at which rune will cast its spell.
172 rune->hp : number of detonations before rune goes away
173 rune->msg : message the rune displays when it goes off
174 rune->direction : direction it will cast a spell in
175 rune->dam : damage the rune will do if it doesn't cast spells
176 rune->attacktype: type of damage it does, if not casting spells
177 rune->other_arch: spell in the rune
178 rune->Cha : how hidden the rune is
179 rune->maxhp : number of spells the rune casts
180 */
181 void
182 move_rune (object *op)
183 {
184 /* runes of level zero cannot detonate. */
185 if (!op->level)
186 return;
187
188 int det = op->invisible;
189
190 if (!(rndm (0, MAX (1, (op->stats.Cha)) - 1)))
191 {
192 op->invisible = 0;
193 op->speed_left -= 1;
194 }
195 else
196 op->invisible = 1;
197
198 if (op->invisible != det)
199 update_object (op, UP_OBJ_CHANGE);
200 }
201
202 /* peterm: rune_attack
203 * function handles those runes which detonate but do not cast spells.
204 */
205 void
206 rune_attack (object *op, object *victim)
207 {
208 if (victim)
209 {
210 hit_player (victim, op->stats.dam, op, op->attacktype, 1);
211
212 if (victim->destroyed ())
213 return;
214
215 /* if there's a disease in the needle, put it in the player */
216 if (op->has_random_items ())
217 create_treasure (op->randomitems, op, 0, (victim->map ? victim->map->difficulty : 1), 0);
218
219 if (op->inv && op->inv->type == DISEASE)
220 {
221 object *disease = op->inv;
222
223 infect_object (victim, disease, 1);
224 disease->destroy ();
225 }
226 }
227 else
228 hit_map (op, 0, op->attacktype, 1);
229 }
230
231 /* This function generalizes attacks by runes/traps. This ought to make
232 * it possible for runes to attack from the inventory,
233 * it'll spring the trap on the victim.
234 */
235 void
236 spring_trap (object *trap, object *victim)
237 {
238 object *env;
239 rv_vector rv;
240 int i;
241
242 /* Prevent recursion */
243 if (trap->stats.hp <= 0)
244 return;
245
246 if (QUERY_FLAG (trap, FLAG_IS_LINKED))
247 use_trigger (trap);
248
249 /* Only living objects can trigger runes that don't cast spells, as
250 * doing direct damage to a non-living object doesn't work anyway.
251 * Typical example is an arrow attacking a door.
252 */
253 if (!QUERY_FLAG (victim, FLAG_ALIVE) && !trap->inv && !trap->other_arch)
254 return;
255
256 trap->stats.hp--; /*decrement detcount */
257
258 if (victim && victim->type == PLAYER)
259 new_draw_info (NDI_UNIQUE, 0, victim, trap->msg);
260
261 /* Flash an image of the trap on the map so the poor sod
262 * knows what hit him.
263 */
264 env = object_get_env_recursive (trap);
265
266 /* If the victim is not next to this trap, don't set it off.
267 * players shouldn't get hit by firing arrows at a door for example.
268 * At the same time, the trap will stick around until detonated
269 */
270 get_rangevector (env, victim, &rv, 0);
271 if (rv.distance > 1)
272 return;
273
274 trap_show (trap, env);
275
276 if (victim->type == PLAYER) // only count players as enemies
277 trap->enemy = victim; // set the victim as the traps enemy, so that summoned
278 // creatures know who to attack.
279
280 /* Only if it is a spell do we proceed here */
281 if ((trap->inv && trap->inv->type == SPELL) || (trap->other_arch && trap->other_arch->clone.type == SPELL))
282 {
283
284 if (trap->destroyed ())
285 return;
286
287 // breaks summon golem spells, which, for inexplicable reasons,
288 // do not work like summon golem spells at all but still require
289 // direction "0" to work at all.
290 //if (trap->direction)
291 // rv.direction = trap->direction;
292
293 for (i = 0; i < MAX (1, trap->stats.maxhp); i++)
294 {
295 if (trap->inv)
296 cast_spell (env, trap, trap->direction, trap->inv, NULL);
297 else
298 {
299 object *spell = arch_to_object (trap->other_arch);
300
301 cast_spell (env, trap, trap->direction, spell, NULL);
302 spell->destroy ();
303 }
304 }
305 }
306 else
307 {
308 rune_attack (trap, victim);
309 if (trap->destroyed ())
310 return;
311 }
312
313 if (trap->stats.hp <= 0)
314 {
315 trap->type = SIGN; /* make the trap impotent */
316 trap->stats.food = 20; /* make it stick around until its spells are gone */
317 SET_FLAG (trap, FLAG_IS_USED_UP);
318 }
319 }
320
321 /* dispel_rune: by peterm
322 * dispels the target rune, depending on the level of the actor
323 * and the level of the rune risk flag, if true, means that there is
324 * a chance that the trap/rune will detonate
325 */
326 int
327 dispel_rune (object *op, object *caster, object *spell, object *skill, int dir)
328 {
329 object *tmp, *tmp2;
330 int searchflag = 1, mflags;
331 sint16 x, y;
332 maptile *m;
333
334 x = op->x + freearr_x[dir];
335 y = op->y + freearr_y[dir];
336 m = op->map;
337
338 mflags = get_map_flags (m, &m, x, y, &x, &y);
339
340 /* Should we perhaps not allow player to disable traps if a monster/
341 * player is standing on top?
342 */
343 if (mflags & P_OUT_OF_MAP)
344 {
345 new_draw_info (NDI_UNIQUE, 0, op, "There's nothing there!");
346 return 0;
347 }
348
349 for (tmp = GET_MAP_OB (m, x, y); tmp != NULL; tmp = tmp->above)
350 {
351 if (tmp->type == RUNE || tmp->type == TRAP)
352 break;
353
354 /* we could put a probability chance here, but since nothing happens
355 * if you fail, no point on that. I suppose we could do a level
356 * comparison so low level players can't erase high level players runes.
357 */
358 if (tmp->type == SIGN && !strcmp (tmp->arch->name, "rune_mark"))
359 {
360 tmp->destroy ();
361 new_draw_info (NDI_UNIQUE, 0, op, "You wipe out the rune of marking!");
362 return 1;
363 }
364
365 /* now search tmp's inventory for traps
366 * This is for chests, where the rune is in the chests inventory.
367 */
368 for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below)
369 {
370 if (tmp2->type == RUNE || tmp2->type == TRAP)
371 {
372 tmp = tmp2;
373 searchflag = 0;
374 break;
375 }
376 }
377 if (!searchflag)
378 break;
379 }
380
381 /* no rune there. */
382 if (tmp == NULL)
383 {
384 new_draw_info (NDI_UNIQUE, 0, op, "There's nothing there!");
385 return 0;
386 }
387 trap_disarm (op, tmp, 0, skill);
388 return 1;
389
390 }
391
392 int
393 trap_see (object *op, object *trap)
394 {
395 int chance;
396
397 chance = random_roll (0, 99, op, PREFER_HIGH);;
398
399 /* decide if we see the rune or not */
400 if ((trap->stats.Cha == 1) || (chance > MIN (95, MAX (5, ((int) ((float) (op->map->difficulty
401 + trap->level + trap->stats.Cha - op->level) / 10.0 * 50.0))))))
402 {
403 new_draw_info_format (NDI_UNIQUE, 0, op, "You spot a %s!", &trap->name);
404 return 1;
405 }
406 return 0;
407 }
408
409 int
410 trap_show (object *trap, object *where)
411 {
412 if (where == NULL)
413 return 0;
414
415 object *tmp = get_archetype ("runedet");
416 tmp->face = GET_ANIMATION (trap, 0);
417 tmp->insert_at (where, 0);
418
419 return 1;
420 }
421
422 int
423 trap_disarm (object *disarmer, object *trap, int risk, object *skill)
424 {
425 int trapworth; /* need to compute the experience worth of the trap
426 before we kill it */
427
428 /* this formula awards a more reasonable amount of exp */
429 trapworth = MAX (1, trap->level) * disarmer->map->difficulty *
430 sqr (MAX (trap->stats.dam, trap->inv ? trap->inv->level : 1)) / skill->level;
431
432 if (!(random_roll (0, (MAX (2, MIN (20, trap->level - skill->level + 5 - disarmer->stats.Dex / 2)) - 1), disarmer, PREFER_LOW)))
433 {
434 new_draw_info_format (NDI_UNIQUE, 0, disarmer, "You successfully disarm the %s!", &trap->name);
435 trap->destroy (1);
436
437 /* If it is your own trap, (or any players trap), don't you don't
438 * get exp for it.
439 */
440 if (trap->owner && trap->owner->type != PLAYER && risk)
441 return trapworth;
442 else
443 return 1; /* give minimal exp and say success */
444 }
445 else
446 {
447 new_draw_info_format (NDI_UNIQUE, 0, disarmer, "You fail to disarm the %s.", &trap->name);
448 if (!(random_roll (0, (MAX (2, skill->level - trap->level + disarmer->stats.Dex / 2 - 6)) - 1, disarmer, PREFER_LOW)) && risk)
449 {
450 new_draw_info (NDI_UNIQUE, 0, disarmer, "In fact, you set it off!");
451 spring_trap (trap, disarmer);
452 }
453 return 0;
454 }
455 }
456
457
458 /* traps need to be adjusted for the difficulty of the map. The
459 * default traps are too strong for wimpy level 1 players, and
460 * unthreatening to anyone of high level
461 */
462
463 void
464 trap_adjust (object *trap, int difficulty)
465 {
466 int i;
467
468 /* now we set the trap level to match the difficulty of the level
469 * the formula below will give a level from 1 to (2*difficulty) with
470 * a peak probability at difficulty
471 */
472
473 trap->level = rndm (0, difficulty - 1) + rndm (0, difficulty - 1);
474 if (trap->level < 1)
475 trap->level = 1;
476
477 /* set the hiddenness of the trap, similar formula to above */
478 trap->stats.Cha = rndm (0, 19) + rndm (0, difficulty - 1) + rndm (0, difficulty - 1);
479
480 if (!trap->other_arch && !trap->inv)
481 {
482 /* set the damage of the trap.
483 * we get 0-4 pts of damage per level of difficulty of the map in
484 * the trap
485 */
486
487 trap->stats.dam = 0;
488 for (i = 0; i < difficulty; i++)
489 trap->stats.dam += rndm (0, 4);
490
491 /* the poison trap special case */
492 if (trap->attacktype & AT_POISON)
493 {
494 trap->stats.dam = rndm (0, difficulty - 1);
495 if (trap->stats.dam < 1)
496 trap->stats.dam = 1;
497 }
498
499 /* so we get an appropriate amnt of exp for AT_DEATH traps */
500 if (trap->attacktype & AT_DEATH)
501 trap->stats.dam = 127;
502 }
503
504 }