Crossfire Server, Branch 1.12
R12190
|
00001 /* 00002 * static char *rcsid_rune_c = 00003 * "$Id: rune.c 11578 2009-02-23 22:02:27Z lalo $"; 00004 */ 00005 00006 /* 00007 CrossFire, A Multiplayer game for X-windows 00008 00009 Copyright (C) 2003,2006 Mark Wedel & Crossfire Development Team 00010 Copyright (C) 1992 Frank Tore Johansen 00011 00012 This program is free software; you can redistribute it and/or modify 00013 it under the terms of the GNU General Public License as published by 00014 the Free Software Foundation; either version 2 of the License, or 00015 (at your option) any later version. 00016 00017 This program is distributed in the hope that it will be useful, 00018 but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 GNU General Public License for more details. 00021 00022 You should have received a copy of the GNU General Public License 00023 along with this program; if not, write to the Free Software 00024 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00025 00026 The authors can be reached via e-mail at crossfire-devel@real-time.com 00027 */ 00028 00035 #include <global.h> 00036 #ifndef __CEXTRACT__ 00037 #include <sproto.h> 00038 #endif 00039 #include <spells.h> 00040 00041 00042 #ifndef sqr 00043 #define sqr(x) ((x)*(x)) 00044 #endif 00045 00046 00066 int write_rune(object *op, object *caster, object *spell, int dir, const char *runename) { 00067 object *tmp, *rune_spell, *rune; 00068 char buf[MAX_BUF]; 00069 mapstruct *m; 00070 sint16 nx, ny; 00071 00072 if (!dir) { 00073 dir = 1; 00074 } 00075 00076 nx = op->x+freearr_x[dir]; 00077 ny = op->y+freearr_y[dir]; 00078 m = op->map; 00079 00080 if (get_map_flags(m, &m, nx, ny, &nx, &ny)) { 00081 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00082 "Can't make a rune there!", NULL); 00083 return 0; 00084 } 00085 for (tmp = GET_MAP_OB(m, nx, ny); tmp != NULL; tmp = tmp->above) 00086 if (tmp->type == RUNE) 00087 break; 00088 00089 if (tmp) { 00090 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00091 "You can't write a rune there.", NULL); 00092 return 0; 00093 } 00094 00095 if (spell->other_arch) { 00096 rune_spell = arch_to_object(spell->other_arch); 00097 } else { 00098 /* Player specified spell. The player has to know the spell, so 00099 * lets just look through the players inventory see if they know it 00100 * use the item_matched_string for our typical matching method. 00101 */ 00102 int bestmatch = 0, ms; 00103 00104 if (!runename || *runename == 0) { 00105 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00106 "Write a rune of what?", NULL); 00107 return 0; 00108 } 00109 00110 rune_spell = NULL; 00111 for (tmp = op->inv; tmp; tmp = tmp->below) { 00112 if (tmp->type == SPELL) { 00113 ms = item_matched_string(op, tmp, runename); 00114 if (ms > bestmatch) { 00115 bestmatch = ms; 00116 rune_spell = tmp; 00117 } 00118 } 00119 } 00120 if (!rune_spell) { 00121 draw_ext_info_format(NDI_UNIQUE, 0, op, 00122 MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00123 "You don't know any spell named %s", 00124 "You don't know any spell named %s", 00125 runename); 00126 return 0; 00127 } 00128 if (rune_spell->skill != spell->skill) { 00129 draw_ext_info_format(NDI_UNIQUE, 0, op, 00130 MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00131 "You can't cast %s with %s", 00132 "You can't cast %s with %s", 00133 rune_spell->name, spell->name); 00134 return 0; 00135 } 00136 if (caster->path_denied&spell->path_attuned) { 00137 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00138 "%s belongs to a spell path denied to you.", 00139 "%s belongs to a spell path denied to you.", 00140 rune_spell->name); 00141 return 0; 00142 } 00143 if (caster_level(caster, rune_spell) < rune_spell->level) { 00144 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00145 "%s is beyond your ability to cast!", 00146 "%s is beyond your ability to cast!", 00147 rune_spell->name); 00148 return 0; 00149 } 00150 if (SP_level_spellpoint_cost(caster, rune_spell, SPELL_MANA) > op->stats.sp) { 00151 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00152 "You don't have enough mana.", NULL); 00153 return 0; 00154 } 00155 if (SP_level_spellpoint_cost(caster, rune_spell, SPELL_GRACE) > op->stats.grace) { 00156 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, 00157 "You don't have enough grace.", NULL); 00158 return 0; 00159 } 00160 op->stats.grace -= SP_level_spellpoint_cost(caster, rune_spell, SPELL_GRACE); 00161 op->stats.sp -= SP_level_spellpoint_cost(caster, rune_spell, SPELL_MANA); 00162 } 00163 /* already proper rune. Note this should only be the case if other_arch was set */ 00164 if (rune_spell->type == RUNE) { 00165 rune = rune_spell; 00166 } else { 00167 rune = create_archetype(GENERIC_RUNE); 00168 snprintf(buf, sizeof(buf), "You set off a rune of %s\n", rune_spell->name); 00169 rune->msg = add_string(buf); 00170 tmp = get_object(); 00171 copy_object(rune_spell, tmp); 00172 insert_ob_in_ob(tmp, rune); 00173 if (spell->face != blank_face) 00174 rune->face = spell->face; 00175 } 00176 rune->level = caster_level(caster, spell); 00177 rune->stats.Cha = rune->level/2; /* the invisibility parameter */ 00178 rune->x = nx; 00179 rune->y = ny; 00180 rune->map = m; 00181 rune->direction = dir; /* where any spell will go upon detonation */ 00182 set_owner(rune, op); /* runes without need no owner */ 00183 set_spell_skill(op, caster, spell, rune); 00184 insert_ob_in_map(rune, m, op, 0); 00185 return 1; 00186 } 00187 00197 static void rune_attack(object *op, object *victim) { 00198 if (victim) { 00199 tag_t tag = victim->count; 00200 hit_player(victim, op->stats.dam, op, op->attacktype, 1); 00201 if (was_destroyed(victim, tag)) 00202 return; 00203 /* if there's a disease in the needle, put it in the player */ 00204 if (HAS_RANDOM_ITEMS(op)) 00205 create_treasure(op->randomitems, op, 0, (victim->map ? victim->map->difficulty : 1), 0); 00206 if (op->inv && op->inv->type == DISEASE) { 00207 object *disease = op->inv; 00208 00209 infect_object(victim, disease, 1); 00210 remove_ob(disease); 00211 free_object(disease); 00212 } 00213 } else 00214 hit_map(op, 0, op->attacktype, 1); 00215 } 00216 00227 void spring_trap(object *trap, object *victim) { 00228 object *env; 00229 tag_t trap_tag = trap->count; 00230 rv_vector rv; 00231 int i, has_spell; 00232 00233 /* Prevent recursion */ 00234 if (trap->stats.hp <= 0) 00235 return; 00236 00237 if (QUERY_FLAG(trap, FLAG_IS_LINKED)) 00238 use_trigger(trap); 00239 00240 /* Check if this trap casts a spell */ 00241 has_spell = ((trap->inv && trap->inv->type == SPELL) || (trap->other_arch && trap->other_arch->clone.type == SPELL)); 00242 00243 env = object_get_env_recursive(trap); 00244 00245 /* If the victim is not next to this trap, and the trap doesn't cast 00246 * a spell, don't set it off. 00247 */ 00248 get_rangevector(env, victim, &rv, 0); 00249 if (rv.distance > 1 && !has_spell) 00250 return; 00251 00252 /* Only living objects can trigger runes that don't cast spells, as 00253 * doing direct damage to a non-living object doesn't work anyway. 00254 * Typical example is an arrow attacking a door. 00255 */ 00256 if (!QUERY_FLAG(victim, FLAG_ALIVE) && !has_spell) 00257 return; 00258 00259 trap->stats.hp--; /*decrement detcount */ 00260 00261 if (victim && victim->type == PLAYER && trap->msg != NULL) 00262 draw_ext_info(NDI_UNIQUE, 0, victim, MSG_TYPE_APPLY, MSG_TYPE_APPLY_TRAP, 00263 trap->msg, trap->msg); 00264 00265 /* Flash an image of the trap on the map so the poor sod 00266 * knows what hit him. 00267 */ 00268 trap_show(trap, env); 00269 00270 /* Only if it is a spell do we proceed here */ 00271 if (has_spell) { 00272 object *spell; 00273 00274 /* This is necessary if the trap is inside something else */ 00275 remove_ob(trap); 00276 trap->x = victim->x; 00277 trap->y = victim->y; 00278 insert_ob_in_map(trap, victim->map, trap, 0); 00279 00280 if (was_destroyed(trap, trap_tag)) 00281 return; 00282 00283 for (i = 0; i < MAX(1, trap->stats.maxhp); i++) { 00284 if (trap->inv) 00285 cast_spell(trap, trap, trap->direction, trap->inv, NULL); 00286 else { 00287 spell = arch_to_object(trap->other_arch); 00288 cast_spell(trap, trap, trap->direction, spell, NULL); 00289 free_object(spell); 00290 } 00291 } 00292 } else { 00293 rune_attack(trap, victim); 00294 if (was_destroyed(trap, trap_tag)) 00295 return; 00296 } 00297 00298 if (trap->stats.hp <= 0) { 00299 trap->type = SIGN; /* make the trap impotent */ 00300 trap->stats.food = 20; /* make it stick around until its spells are gone */ 00301 SET_FLAG(trap, FLAG_IS_USED_UP); 00302 } 00303 } 00304 00323 int dispel_rune(object *op, object *caster, object *spell, object *skill, int dir) { 00324 object *tmp, *tmp2; 00325 int searchflag = 1, mflags; 00326 sint16 x, y; 00327 mapstruct *m; 00328 00329 x = op->x+freearr_x[dir]; 00330 y = op->y+freearr_y[dir]; 00331 m = op->map; 00332 00333 mflags = get_map_flags(m, &m, x, y, &x, &y); 00334 00335 /* Should we perhaps not allow player to disable traps if a monster/ 00336 * player is standing on top? 00337 */ 00338 if (mflags&P_OUT_OF_MAP) { 00339 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00340 "There's nothing there!", NULL); 00341 return 0; 00342 } 00343 00344 /* This can happen if a player does a 'magic rune of dispel'. Without 00345 * any skill, chance of success is zero, and we don't know who to tell 00346 * (as otherwise we would have a skill pointer). Plus, trap_disarm() 00347 * presumes skill is not null and will crash if it is. 00348 */ 00349 if (!skill) 00350 return 0; 00351 00352 for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above) { 00353 if (tmp->type == RUNE || tmp->type == TRAP) 00354 break; 00355 00356 /* we could put a probability chance here, but since nothing happens 00357 * if you fail, no point on that. I suppose we could do a level 00358 * comparison so low level players can't erase high level players runes. 00359 */ 00360 if (tmp->type == SIGN && !strcmp(tmp->arch->name, "rune_mark")) { 00361 remove_ob(tmp); 00362 free_object(tmp); 00363 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_SUCCESS, 00364 "You wipe out the rune of marking!", NULL); 00365 return 1; 00366 } 00367 00368 /* now search tmp's inventory for traps 00369 * This is for chests, where the rune is in the chests inventory. 00370 */ 00371 for (tmp2 = tmp->inv; tmp2 != NULL; tmp2 = tmp2->below) { 00372 if (tmp2->type == RUNE || tmp2->type == TRAP) { 00373 tmp = tmp2; 00374 searchflag = 0; 00375 break; 00376 } 00377 } 00378 if (!searchflag) 00379 break; 00380 } 00381 00382 /* no rune there. */ 00383 if (tmp == NULL) { 00384 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, 00385 "There's nothing there!", NULL); 00386 return 0; 00387 } 00388 trap_disarm(op, tmp, 0, skill); 00389 return 1; 00390 } 00391 00403 int trap_see(object *op, object *trap) { 00404 int chance; 00405 00406 chance = random_roll(0, 99, op, PREFER_HIGH); 00407 00408 /* decide if we see the rune or not */ 00409 if ((trap->stats.Cha == 1) 00410 || (chance > MIN(95, MAX(5, ((int)((float)(op->map->difficulty+trap->level+trap->stats.Cha-op->level)/10.0*50.0)))))) { 00411 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, 00412 "You spot a %s!", 00413 "You spot a %s!", 00414 trap->name); 00415 return 1; 00416 } 00417 return 0; 00418 } 00419 00431 int trap_show(object *trap, object *where) { 00432 object *tmp2; 00433 00434 if (where == NULL) 00435 return 0; 00436 tmp2 = create_archetype("runedet"); 00437 tmp2->face = &new_faces[GET_ANIMATION(trap, 0)]; 00438 tmp2->x = where->x; 00439 tmp2->y = where->y; 00440 tmp2->map = where->map; 00441 insert_ob_in_map(tmp2, where->map, NULL, 0); 00442 return 1; 00443 } 00444 00459 int trap_disarm(object *disarmer, object *trap, int risk, object *skill) { 00460 int trapworth; /* need to compute the experience worth of the trap 00461 before we kill it */ 00462 00463 /* this formula awards a more reasonable amount of exp */ 00464 trapworth = MAX(1, trap->level)*disarmer->map->difficulty* 00465 sqr(MAX(trap->stats.dam, trap->inv ? trap->inv->level : 1))/skill->level; 00466 00467 if (!(random_roll(0, (MAX(2, MIN(20, trap->level-skill->level+5-disarmer->stats.Dex/2))-1), disarmer, PREFER_LOW))) { 00468 draw_ext_info_format(NDI_UNIQUE, 0, disarmer, 00469 MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, 00470 "You successfully disarm the %s!", 00471 "You successfully disarm the %s!", 00472 trap->name); 00473 destroy_object(trap); 00474 /* If it is your own trap, (or any players trap), don't you don't 00475 * get exp for it. 00476 */ 00477 if (trap->owner && trap->owner->type != PLAYER && risk) 00478 return trapworth; 00479 else 00480 return 1; /* give minimal exp and say success */ 00481 } else { 00482 draw_ext_info_format(NDI_UNIQUE, 0, disarmer, 00483 MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE, 00484 "You fail to disarm the %s.", 00485 "You fail to disarm the %s.", 00486 trap->name); 00487 if (!(random_roll(0, (MAX(2, skill->level-trap->level+disarmer->stats.Dex/2-6))-1, disarmer, PREFER_LOW)) 00488 && risk) { 00489 draw_ext_info(NDI_UNIQUE, 0, disarmer, 00490 MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, 00491 "In fact, you set it off!", NULL); 00492 spring_trap(trap, disarmer); 00493 } 00494 return 0; 00495 } 00496 } 00497 00508 void trap_adjust(object *trap, int difficulty) { 00509 int i; 00510 00511 /* now we set the trap level to match the difficulty of the level 00512 * the formula below will give a level from 1 to (2*difficulty) with 00513 * a peak probability at difficulty 00514 */ 00515 00516 trap->level = rndm(0, difficulty-1)+rndm(0, difficulty-1); 00517 if (trap->level < 1) 00518 trap->level = 1; 00519 00520 /* set the hiddenness of the trap, similar formula to above */ 00521 trap->stats.Cha = rndm(0, 19)+rndm(0, difficulty-1)+rndm(0, difficulty-1); 00522 00523 if (!trap->other_arch && !trap->inv) { 00524 /* set the damage of the trap. 00525 * we get 0-4 pts of damage per level of difficulty of the map in 00526 * the trap 00527 */ 00528 00529 trap->stats.dam = 0; 00530 for (i = 0; i < difficulty; i++) 00531 trap->stats.dam += rndm(0, 4); 00532 00533 /* the poison trap special case */ 00534 if (trap->attacktype&AT_POISON) { 00535 trap->stats.dam = rndm(0, difficulty-1); 00536 if (trap->stats.dam < 1) 00537 trap->stats.dam = 1; 00538 } 00539 00540 /* so we get an appropriate amnt of exp for AT_DEATH traps */ 00541 if (trap->attacktype&AT_DEATH) 00542 trap->stats.dam = 127; 00543 } 00544 00545 }