ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/rune.C
Revision: 1.4
Committed: Sun Sep 10 15:59:57 2006 UTC (17 years, 8 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.3: +384 -324 lines
Log Message:
indent

File Contents

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