nnextcitem.cpp
1 //------------------------------------------------------------------------- 2 /* 3 Copyright (C) 2010-2019 EDuke32 developers and contributors 4 Copyright (C) 2019 Nuke.YKT 5 Copyright (C) NoOne 6 7 ********************************************************************* 8 NoOne: Custom Item system. Allows to create custom user pickups. 9 Potentially can be used to describe and store all the 10 vanilla items in text file. 11 12 For full documentation visit: http://cruo.bloodgame.ru/xxsystem/citem/ 13 ********************************************************************* 14 15 This file is part of NBlood. 16 17 NBlood is free software; you can redistribute it and/or 18 modify it under the terms of the GNU General Public License version 2 19 as published by the Free Software Foundation. 20 21 This program is distributed in the hope that it will be useful, 22 but WITHOUT ANY WARRANTY; without even the implied warranty of 23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 24 25 See the GNU General Public License for more details. 26 27 You should have received a copy of the GNU General Public License 28 along with this program; if not, write to the Free Software 29 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 30 */ 31 //------------------------------------------------------------------------- 32 33 #ifdef NOONE_EXTENSIONS 34 35 #include <stdlib.h> 36 #include <string.h> 37 #include "compat.h" 38 #include "build.h" 39 #include "mmulti.h" 40 #include "globals.h" 41 #include "common_game.h" 42 #include "player.h" 43 #include "network.h" 44 #include "weapon.h" 45 #include "sfx.h" 46 47 #include "nnextstr.h" 48 #include "nnexts.h" 49 50 51 /** DEFINITIONS 52 ********************************************************************************/ 53 #define kItemDescriptorVerMajor 1 54 55 #define kItemUserBase kItemWeaponBase 56 #define kItemUserMax kDudeBase 57 #define kMaxUserItems kItemUserMax - kItemUserBase 58 59 typedef char (*ITEMACTIONPROC)(PLAYER* pPlayer, ACTIONARG* a); 60 61 char gUserItemsInitialized = 0; 62 63 /** ITEM parameters mostly for parser 64 ********************************************************************************/ 65 enum enum_PAR_MAIN_ITEM_PARAMS 66 { 67 kParItemType = 0, 68 kParItemMessage, 69 kParItemAppearance, 70 kParItemRespawn, 71 kParItemLiveTime, 72 kParItemGameMode, 73 kParItemFlags, 74 }; 75 static const char* gParItemMain[] = 76 { 77 "Type", 78 "Message", 79 "Appearance", 80 "RespawnTime", 81 "DropLiveTime", 82 "GameMode", 83 "Flags", 84 NULL, 85 }; 86 87 enum enum_ITEM_GROUP 88 { 89 kItemGroupItem = 0, 90 kItemGroupWeapon, 91 kItemGroupAmmo, 92 kItemGroupArmor, 93 kItemGroupHealth, 94 kItemGroupPowerup, 95 kItemGroupKey, 96 kItemGroupMax, 97 }; 98 static const char* gParItemGroup[] = 99 { 100 "Item", 101 "Weapon", 102 "Ammo", 103 "Armor", 104 "Health", 105 "Powerup", 106 "Key", 107 NULL, 108 }; 109 110 enum enum_PAR_APPEARANCE 111 { 112 kParItemAppearTile = 0, 113 kParItemAppearSeq, 114 kParItemAppearSize, 115 kParItemAppearPal, 116 kParItemAppearShade, 117 kParItemAppearSnd, 118 }; 119 static const char* gParItemAppearEntry[] = 120 { 121 "Tile", 122 "Seq", 123 "Size", 124 "Pal", 125 "Shade", 126 "Sound", 127 NULL, 128 }; 129 130 enum enum_PAR_FLAGS 131 { 132 kParItemFlagsNoEff = 0, 133 kParItemFlagsNoMsg, 134 kParItemFlagsShared, 135 kParItemFlagsExtLimits, 136 }; 137 static const char* gParItemFlags[] = 138 { 139 "NoEffect", 140 "NoMessage", 141 "Shared", 142 "ExtLimits", 143 NULL, 144 }; 145 146 enum enum_PAR_RESPAWNTIME 147 { 148 kRespawnTimeSpecial1 = 0, 149 kRespawnTimeSpecial2, 150 }; 151 152 const char* gParRespawnTime[] = 153 { 154 "SPECIAL1", 155 "SPECIAL2", 156 NULL, 157 }; 158 159 enum enum_PAR_ITEM_GAMETYPE_FLAGS 160 { 161 kParItemGameS = 0, 162 kParItemGameB, 163 kParItemGameC, 164 kParItemGameT, 165 }; 166 static const char* gParItemGametype[] = 167 { 168 "S", 169 "B", 170 "C", 171 "T", 172 NULL, 173 }; 174 175 enum enum_ACTION_DEST 176 { 177 kItemActionHealth = 0, 178 kItemActionArmor, 179 kItemActionAmmo, 180 kItemActionWeapon, 181 kItemActionPowerTime, 182 kItemActionKey, 183 kItemActionPack, 184 kItemActionEffect, 185 kItemActionAirTime, 186 kItemActionDmgIgnore, 187 kItemActionTeamScore, 188 }; 189 static const char* gParItemActType[] = 190 { 191 "Health", 192 "Armor", 193 "Ammo", 194 "Weapon", 195 "PowerTime", 196 "Key", 197 "Inventory", 198 "ScreenEffect", 199 "AirTime", 200 "IgnoreDamage", 201 "TeamScore", 202 "Frag", 203 NULL, 204 }; 205 206 enum enum_PAR_ACTION_ENTRY 207 { 208 kParActionAmount = 0, 209 kParActionAmountMin, 210 kParActionAmountMax, 211 kParActionSlot, 212 kParActionReq, 213 kParActionCompat, 214 }; 215 static const char* gParItemActEntry[] = 216 { 217 "Amount", 218 "MinAmount", 219 "MaxAmount", 220 "Slot", 221 "Required", 222 "Compatible", 223 NULL, 224 }; 225 226 227 enum enum_PAR_ACTION_TYPE 228 { 229 kParItemActionSet = 0, 230 kParItemActionAdd, 231 kParItemActionSub, 232 }; 233 static const char* gParItemActOperator[] = 234 { 235 "Set", 236 "Add", 237 "Sub", 238 NULL, 239 }; 240 241 242 /** ACTION functions prototypes 243 ************************************************************************************************/ 244 static char userItemDoAction(PLAYER* pPlayer, ACTIONARG* a); 245 static char ACTION_ChangeArmor(PLAYER* pPlayer, ACTIONARG* a); 246 static char ACTION_ChangeHealth(PLAYER* pPlayer, ACTIONARG* a); 247 static char ACTION_ChangeKey(PLAYER* pPlayer, ACTIONARG* a); 248 static char ACTION_ChangeAmmo(PLAYER* pPlayer, ACTIONARG* a); 249 static char ACTION_ChangeWeapon(PLAYER* pPlayer, ACTIONARG* a); 250 static char ACTION_ChangePackItem(PLAYER* pPlayer, ACTIONARG* a); 251 static char ACTION_ChangePowerupTime(PLAYER* pPlayer, ACTIONARG* a); 252 static char ACTION_ChangeEffect(PLAYER* pPlayer, ACTIONARG* a); 253 static char ACTION_ChangeAirTime(PLAYER* pPlayer, ACTIONARG* a); 254 static char ACTION_ChangeIgnoreDmg(PLAYER* pPlayer, ACTIONARG* a); 255 static char ACTION_ChangeTeamScore(PLAYER* pPlayer, ACTIONARG* a); 256 static char ACTION_ChangeFrag(PLAYER* pPlayer, ACTIONARG* a); 257 258 259 ITEMACTIONPROC gItemActFunc[] = 260 { 261 ACTION_ChangeHealth, 262 ACTION_ChangeArmor, 263 ACTION_ChangeAmmo, 264 ACTION_ChangeWeapon, 265 ACTION_ChangePowerupTime, 266 ACTION_ChangeKey, 267 ACTION_ChangePackItem, 268 ACTION_ChangeEffect, 269 ACTION_ChangeAirTime, 270 ACTION_ChangeIgnoreDmg, 271 ACTION_ChangeTeamScore, 272 ACTION_ChangeFrag, 273 }; 274 275 276 /** VARIOUS helpers 277 ************************************************************************************************/ 278 static char canPickupPermanentWeapon(PLAYER* pPlayer, spritetype* pISpr, ITEM* pItem); 279 static int helperChangeValue(int nValue, ITEM::ACTION* pAct); 280 281 282 /** ARRAY of pointers where we store all the items 283 ********************************************************************************/ 284 static ITEM* gItems[kMaxUserItems]; 285 286 287 /** DEFAULT pickup sounds 288 ********************************************************************************/ 289 static const uint16_t gItemGroupSnd[kItemGroupMax] = 290 { 291 775, // item 292 777, // weapon 293 782, // ammo 294 779, // armor 295 780, // health 296 776, // powerup 297 781, // key 298 }; 299 300 /** REPLACING these may ruin multiplayer? 301 ********************************************************************************/ 302 static const uint8_t gBannedItems[] = 303 { 304 kItemFlagA, 305 kItemFlagABase, 306 kItemFlagB, 307 kItemFlagBBase, 308 }; 309 310 /** EXTERNAL game functions to handle new items 311 ********************************************************************************/ 312 ITEM* userItemGet(int nType) 313 { 314 if (rngok(nType, kItemUserBase, kItemUserMax)) 315 return gItems[nType - kItemUserBase]; 316 317 return NULL; 318 } 319 320 321 char userItemPickup(PLAYER* pPlayer, spritetype* pISpr, ITEM* pItem) 322 { 323 char isShared = ((pItem->flags & kFlagItemShared) != 0); 324 XSPRITE* pXISpr = &xsprite[pISpr->extra]; 325 GAMEOPTIONS* pOpt = &gGameOptions; 326 ACTIONARG data; 327 328 int nGame = pOpt->nGameType; 329 int i, r = 1, testReq = 0; 330 331 memset(&data, 0, sizeof(data)); 332 if (pItem->flags & kFlagItemNoPickup) 333 { 334 if (nGame == kGameTypeSinglePlayer && (pItem->flags & kFlagItemNoPickupS)) return 0; 335 else if (nGame == kGameTypeCoop && (pItem->flags & kFlagItemNoPickupC)) return 0; 336 else if (nGame == kGameTypeBloodBath && (pItem->flags & kFlagItemNoPickupB)) return 0; 337 else if (nGame == kGameTypeTeams && (pItem->flags & kFlagItemNoPickupT)) return 0; 338 } 339 340 // Unfortunately, a special check required 341 // for weapon items with permanent respawn 342 // option... 343 344 if (pItem->group == kItemGroupWeapon) 345 { 346 if (pOpt->nWeaponSettings == 1 || pXISpr->respawn == 3) 347 { 348 if (!canPickupPermanentWeapon(pPlayer, pISpr, pItem)) 349 return 0; 350 } 351 } 352 353 if (pItem->action != NULL) 354 { 355 testReq = ((pItem->flags & kFlagItemHasReqActions) != 0); 356 357 // Test required actions (if any) for 358 // success on first pass, do actions 359 // on second. 360 361 while(testReq >= 0) 362 { 363 data.act = pItem->action; 364 r = 0; 365 366 for (i = 0; i < pItem->numactions; i++, data.act++) 367 { 368 data.isShared = ((isShared > 0) | (data.act->type == kItemActionKey && pOpt->nKeySettings == 2)); 369 data.isCompat = ((data.act->flags & kFlagActionCompat) != 0); 370 data.isTest = (testReq > 0); 371 372 if (!userItemDoAction(pPlayer, &data)) 373 { 374 if (data.act->flags & kFlagActionReq) 375 return 0; 376 } 377 else 378 { 379 r++; 380 } 381 } 382 383 if (r) 384 { 385 for (i = 0; i < pItem->numeffects; i++, data.act++) 386 { 387 data.isCompat = ((data.act->flags & kFlagActionCompat) != 0); 388 data.isShared = (isShared > 0); 389 data.isTest = (testReq > 0); 390 391 if (!userItemDoAction(pPlayer, &data)) 392 { 393 if (data.act->flags & kFlagActionReq) 394 return 0; 395 } 396 else 397 { 398 r++; 399 } 400 } 401 } 402 403 testReq--; 404 } 405 } 406 407 if (r) 408 { 409 if (pItem->appearance.sound) 410 sfxPlay3DSound(pPlayer->pSprite, pItem->appearance.sound, -1, 0); 411 } 412 413 return (r > 0); 414 } 415 416 spritetype* userItemDrop(spritetype* pSpr, int nType) 417 { 418 spritetype* pISpr; ITEM* pItem; 419 if (!pSpr || pSpr->statnum >= kMaxStatus 420 || (pItem = userItemGet(nType)) == NULL || (pISpr = actSpawnFloor(pSpr)) == NULL) 421 return NULL; 422 423 pISpr->type = nType; 424 pISpr->picnum = pItem->appearance.picnum; 425 pISpr->pal = pItem->appearance.pal; 426 pISpr->shade = pItem->appearance.shade; 427 pISpr->xrepeat = pItem->appearance.xrepeat; 428 pISpr->yrepeat = pItem->appearance.yrepeat; 429 430 if (pItem->appearance.seq) 431 { 432 if (pISpr->extra <= 0) 433 dbInsertXSprite(pISpr->index); 434 435 seqSpawn(pItem->appearance.seq, OBJ_SPRITE, pISpr->extra); 436 } 437 438 if (pItem->droplivetime) 439 evPost(pISpr->index, OBJ_SPRITE, pItem->droplivetime * 100, kCallbackRemove); 440 441 return pISpr; 442 } 443 444 int userItemGetRespawnTime(spritetype* pSpr) 445 { 446 GAMEOPTIONS* pOpt = &gGameOptions; 447 XSPRITE* pXSpr; ITEM* pItem; 448 int nTime = -1; 449 450 if (pSpr->extra <= 0 451 || (pItem = userItemGet(pSpr->type)) == NULL) 452 return -1; 453 454 pXSpr = &xsprite[pSpr->extra]; 455 456 switch (pItem->group) 457 { 458 case kItemGroupAmmo: 459 if (pXSpr->respawn == 2 || (pXSpr->respawn != 1 && pOpt->nWeaponSettings != 0)) 460 { 461 nTime = pOpt->nWeaponRespawnTime; 462 break; 463 } 464 return -1; 465 case kItemGroupWeapon: 466 if (pXSpr->respawn == 3 || pOpt->nWeaponSettings == 1) return 0; 467 else if (pXSpr->respawn != 1 && pOpt->nWeaponSettings != 0) 468 { 469 nTime = pOpt->nWeaponRespawnTime; 470 break; 471 } 472 return -1; 473 default: 474 if (pXSpr->respawn == 3 && pOpt->nGameType == kGameTypeCoop) return 0; 475 if (pXSpr->respawn == 2 || (pXSpr->respawn != 1 && pOpt->nItemSettings != 0)) 476 { 477 if (pItem->flags & kFlagItemRespawnSpec1) nTime = pOpt->nSpecialRespawnTime; 478 else if (pItem->flags & kFlagItemRespawnSpec2) nTime = pOpt->nSpecialRespawnTime<<1; 479 else nTime = pOpt->nItemRespawnTime; 480 481 break; 482 } 483 return -1; 484 } 485 486 if (pItem->respawntime > 0) 487 nTime = pItem->respawntime * 100; 488 489 return nTime; 490 } 491 492 char userItemViewUseRespawnMarkers(ITEM* pItem) 493 { 494 if (pItem) 495 { 496 if (pItem->group == kItemGroupWeapon) 497 return gGameOptions.nWeaponSettings == 3; 498 499 return gGameOptions.nItemSettings == 2; 500 } 501 502 return 0; 503 } 504 505 void userItemsUninit() 506 { 507 int i = kMaxUserItems; 508 while (--i >= 0) 509 { 510 if (gItems[i]) 511 { 512 CUSTOMITEM_SETUP::ClearItem(gItems[i]); 513 free(gItems[i]), gItems[i] = NULL; 514 } 515 } 516 } 517 518 int userItemsInit(char showLog) 519 { 520 DICTNODE* hRes = gSysRes.Lookup("ITEMS", "ITM"); 521 IniFile* pIni; int c = 0; 522 523 if (!hRes || hRes->size <= 0) 524 return 0; 525 526 CUSTOMITEM_SETUP::showLog = showLog; 527 528 pIni = new IniFile((unsigned char*)gSysRes.Load(hRes), hRes->size, INI_SKIPCM|INI_SKIPZR); 529 c += CUSTOMITEM_SETUP::Setup(pIni); 530 delete(pIni); 531 532 CUSTOMITEM_SETUP::Message 533 ( 534 "\n" 535 "\n" 536 "\tUser items added...........: %d\n" 537 "\tUser items replaced........: %d\n" 538 "\tGame items replaced........: %d\n" 539 "\tFailed to process..........: %d\n" 540 "\tTotal......................: %d\n", 541 CUSTOMITEM_SETUP::numNewItemsAdded, 542 CUSTOMITEM_SETUP::numUserItemsReplaced, 543 CUSTOMITEM_SETUP::numGameItemsReplaced, 544 CUSTOMITEM_SETUP::numFailedItems, 545 c 546 ); 547 548 return c; 549 } 550 551 void userItemsInitSprites() 552 { 553 ITEM* pItem; spritetype* pISpr; 554 int i; 555 556 for (i = headspritestat[kStatItem]; i >= 0; i = nextspritestat[i]) 557 { 558 pISpr = &sprite[i]; 559 if ((pItem = userItemGet(pISpr->type)) == NULL) 560 continue; 561 562 if (pItem->appearance.seq) 563 { 564 if (pISpr->extra <= 0) 565 dbInsertXSprite(pISpr->index); 566 567 seqSpawn(pItem->appearance.seq, OBJ_SPRITE, pISpr->extra); 568 } 569 } 570 } 571 572 static char canPickupPermanentWeapon(PLAYER* pPlayer, spritetype* pISpr, ITEM* pItem) 573 { 574 ITEM::ACTION* pAct = pItem->action; 575 int i, n; 576 577 for (i = 0; i < pItem->numactions; i++, pAct++) 578 { 579 if (pAct->type == kItemActionWeapon) 580 { 581 n = pAct->slot+1; 582 if (!pPlayer->hasWeapon[n] || userItemGetRespawnTime(pISpr)) 583 return 1; 584 585 break; 586 } 587 } 588 589 return 0; 590 } 591 592 /** ACTION functions 593 ********************************************************************************/ 594 static char userItemDoAction(PLAYER* pPlayer, ACTIONARG* a) 595 { 596 char r = 0; int i; 597 598 if (a->isShared) 599 { 600 for (i = connecthead; i >= 0; i = connectpoint2[i]) 601 if (gItemActFunc[a->act->type](&gPlayer[i], a)) 602 r = 1; 603 } 604 else 605 { 606 r = gItemActFunc[a->act->type](pPlayer, a); 607 } 608 609 return r; 610 } 611 612 static char ACTION_ChangeArmor(PLAYER* pPlayer, ACTIONARG* a) 613 { 614 int nSlot = a->act->slot; 615 int nCur = pPlayer->armor[nSlot]; 616 int i; 617 618 nCur = helperChangeValue(nCur, a->act); 619 if (nCur == pPlayer->armor[nSlot]) 620 { 621 if (!a->isCompat) 622 return 0; 623 624 i = LENGTH(PLAYER::armor); 625 while (--i >= 0) 626 { 627 if (pPlayer->armor[i] >> 4 < 100) 628 break; 629 } 630 631 if (i < 0) 632 return 0; 633 } 634 635 if (!a->isTest) 636 pPlayer->armor[nSlot] = nCur; 637 638 return 1; 639 } 640 641 static char ACTION_ChangeHealth(PLAYER* pPlayer, ACTIONARG* a) 642 { 643 int nCur = pPlayer->pXSprite->health; 644 int nOld = nCur; 645 646 nCur = helperChangeValue(nCur, a->act); 647 if (nCur == nOld) 648 return 0; 649 650 651 if (!a->isTest) 652 { 653 if (nCur > nOld) 654 { 655 pPlayer->pXSprite->health = nCur; 656 } 657 else 658 { 659 playerDamageSprite(pPlayer->nSprite, pPlayer, kDamageFall, nOld - nCur); 660 if (pPlayer->pSprite->extra > 0 && pPlayer->pXSprite->health == nOld) 661 return 0; 662 } 663 } 664 665 return 1; 666 } 667 668 static char ACTION_ChangeKey(PLAYER* pPlayer, ACTIONARG* a) 669 { 670 int nSlot = a->act->slot; 671 int nCur = pPlayer->hasKey[nSlot]; 672 673 nCur = helperChangeValue(nCur, a->act); 674 if (nCur == (int)pPlayer->hasKey[nSlot]) 675 return 0; 676 677 if (!a->isTest) 678 pPlayer->hasKey[nSlot] = nCur; 679 680 return 1; 681 } 682 683 static void helperForceLowerWeapon(PLAYER* pPlayer) 684 { 685 // force lowering 686 pPlayer->weaponState = 0; 687 pPlayer->weaponAmmo = 0; 688 pPlayer->weaponTimer = 0; 689 pPlayer->weaponQav = -1; 690 pPlayer->throwPower = 0; 691 pPlayer->throwTime = 0; 692 pPlayer->fuseTime = 0; 693 pPlayer->qavLoop = 0; 694 695 pPlayer->curWeapon = kWeaponPitchfork; 696 } 697 698 static char ACTION_ChangeAmmo(PLAYER* pPlayer, ACTIONARG* a) 699 { 700 int nSlot = a->act->slot, nCur = pPlayer->ammoCount[nSlot]; 701 AMMOITEMDATA* pInfo; 702 int wSlot; 703 704 nCur = helperChangeValue(nCur, a->act); 705 if (nCur == pPlayer->ammoCount[nSlot]) 706 return 0; 707 708 if (nCur < pPlayer->ammoCount[nSlot] && gInfiniteAmmo) 709 return 0; 710 711 if (!a->isTest) 712 { 713 pInfo = gAmmoItemData; 714 while (pInfo->type && pInfo->weaponType && pInfo->type != nSlot) pInfo++; 715 if ((wSlot = pInfo->weaponType) > 0 && nCur > 0) 716 pPlayer->hasWeapon[wSlot] = 1; 717 718 pPlayer->ammoCount[nSlot] = nCur; 719 720 if (wSlot > 0) 721 { 722 if (nCur <= 0 && pPlayer->curWeapon == wSlot) 723 helperForceLowerWeapon(pPlayer); 724 } 725 } 726 727 return 1; 728 } 729 730 static char ACTION_ChangeWeapon(PLAYER* pPlayer, ACTIONARG* a) 731 { 732 GAMEOPTIONS* pOpt = &gGameOptions; 733 int wSlot = a->act->slot+1, aSlot = wSlot - 1; 734 int nCur = pPlayer->hasWeapon[wSlot]; 735 int nOld = nCur; 736 737 nCur = helperChangeValue(nCur, a->act); 738 739 if (nCur) 740 { 741 if ((wSlot == kWeaponLifeLeech) 742 && (pOpt->nGameType >= kGameTypeBloodBath) && findDroppedLeech(pPlayer, NULL)) 743 return 0; 744 745 if (pPlayer->hasWeapon[wSlot]) 746 { 747 if (pPlayer->ammoCount[aSlot] >= a->act->amount[2]) 748 return 0; 749 750 if (pOpt->nWeaponSettings != 2 751 && pOpt->nWeaponSettings != 3) 752 return 0; 753 } 754 } 755 else if (nCur == nOld) 756 { 757 return 0; 758 } 759 760 if (!a->isTest) 761 { 762 pPlayer->hasWeapon[wSlot] = nCur; 763 764 if (nCur) 765 { 766 if (pPlayer->ammoCount[aSlot]) 767 { 768 if ((wSlot = WeaponUpgrade(pPlayer, wSlot)) != pPlayer->curWeapon) 769 pPlayer->weaponState = 0, pPlayer->nextWeapon = wSlot; 770 } 771 } 772 else if (pPlayer->curWeapon == wSlot) 773 { 774 helperForceLowerWeapon(pPlayer); 775 } 776 } 777 778 return 1; 779 } 780 781 static char ACTION_ChangePackItem(PLAYER* pPlayer, ACTIONARG* a) 782 { 783 int nSlot = a->act->slot; PACKINFO* pSlot = &pPlayer->packSlots[nSlot]; 784 int nCur = pSlot->curAmount; 785 786 nCur = helperChangeValue(nCur, a->act); 787 if (nCur == pSlot->curAmount) 788 return 0; 789 790 if (!a->isTest) 791 { 792 pSlot->curAmount = nCur; 793 if (pPlayer->packItemId < 0 || !pPlayer->packSlots[pPlayer->packItemId].curAmount) 794 pPlayer->packItemId = nSlot; 795 } 796 797 return 1; 798 } 799 800 801 static char ACTION_ChangePowerupTime(PLAYER* pPlayer, ACTIONARG* a) 802 { 803 int nSlot = a->act->slot; 804 int nCur = pPlayer->pwUpTime[nSlot]; 805 POWERUPINFO* pInfo = &gPowerUpInfo[nSlot]; 806 int t; 807 808 if ((t = powerupCheck(pPlayer, nSlot)) > 0 && a->isCompat) 809 { 810 if (pInfo->pickupOnce) 811 return 0; 812 813 return 1; // pickup without changing time... 814 } 815 816 nCur = helperChangeValue(nCur, a->act); 817 if (nCur == pPlayer->pwUpTime[nSlot]) 818 return 0; 819 820 if (!a->isTest) 821 { 822 if (nCur) 823 { 824 if (t <= 0) 825 { 826 powerupActivate(pPlayer, nSlot); 827 sfxKill3DSound(pPlayer->pSprite, -1, -1); 828 } 829 } 830 else 831 { 832 if (t > 0) 833 powerupDeactivate(pPlayer, nSlot); 834 } 835 836 pPlayer->pwUpTime[nSlot] = nCur; 837 } 838 839 return 1; 840 } 841 842 static char ACTION_ChangeEffect(PLAYER* pPlayer, ACTIONARG* a) 843 { 844 int nSlot = a->act->slot; 845 int nCur = playerEffectGet(pPlayer, nSlot); 846 int nOld = nCur; 847 848 nCur = helperChangeValue(nCur, a->act); 849 if (nCur == nOld) 850 return 0; 851 852 if (!a->isTest) 853 playerEffectSet(pPlayer, nSlot, nCur); 854 855 return 1; 856 } 857 858 static char ACTION_ChangeAirTime(PLAYER* pPlayer, ACTIONARG* a) 859 { 860 int nCur = pPlayer->underwaterTime; 861 862 nCur = helperChangeValue(nCur, a->act); 863 if (nCur == pPlayer->underwaterTime) 864 return 0; 865 866 if (!a->isTest) 867 pPlayer->underwaterTime = nCur; 868 869 return 1; 870 } 871 872 static char ACTION_ChangeIgnoreDmg(PLAYER* pPlayer, ACTIONARG* a) 873 { 874 int nSlot = a->act->slot; 875 int nCur = pPlayer->damageControl[nSlot]; 876 877 nCur = helperChangeValue(nCur, a->act); 878 if (nCur == pPlayer->damageControl[nSlot]) 879 return 0; 880 881 if (!a->isTest) 882 pPlayer->damageControl[nSlot] = nCur; 883 884 return 1; 885 } 886 887 static char ACTION_ChangeTeamScore(PLAYER* pPlayer, ACTIONARG* a) 888 { 889 if (gGameOptions.nGameType == kGameTypeTeams) 890 { 891 int nCur = gPlayerScores[pPlayer->teamId]; 892 int nOld = nCur; 893 894 nCur = helperChangeValue(nCur, a->act); 895 if (nCur == nOld) 896 return 0; 897 898 if (!a->isTest) 899 { 900 gPlayerScores[pPlayer->teamId] = nCur; 901 gPlayerScoreTicks[pPlayer->teamId] += 30; 902 } 903 904 return 1; 905 } 906 907 return 0; 908 } 909 910 static char ACTION_ChangeFrag(PLAYER* pPlayer, ACTIONARG* a) 911 { 912 if (gGameOptions.nGameType != kGameTypeCoop) 913 { 914 int nCur = pPlayer->fragCount; 915 916 nCur = helperChangeValue(nCur, a->act); 917 if (nCur == pPlayer->fragCount) 918 return 0; 919 920 if (!a->isTest) 921 pPlayer->fragCount = nCur; 922 923 return 1; 924 } 925 926 return 0; 927 } 928 929 static int helperChangeValue(int nValue, ITEM::ACTION* pAct) 930 { 931 int32_t n = pAct->amount[0]; 932 int32_t l = pAct->amount[1]; 933 int32_t g = pAct->amount[2]; 934 935 switch (pAct->operation) 936 { 937 case kParItemActionSet: 938 if (!irngok(nValue, l, g)) break; 939 nValue = ClipRange(n, l, g); 940 break; 941 case kParItemActionAdd: 942 if (nValue > g) break; 943 nValue = ClipHigh(nValue + n, g); 944 break; 945 case kParItemActionSub: 946 if (nValue < l) break; 947 nValue = ClipLow(nValue - n, l); 948 break; 949 } 950 951 return nValue; 952 } 953 954 /** ITEM parser and setup code part 955 ********************************************************************************/ 956 ITEM CUSTOMITEM_SETUP::buffer; 957 IniFile* CUSTOMITEM_SETUP::pDesc = NULL; 958 int CUSTOMITEM_SETUP::version = 0; 959 int CUSTOMITEM_SETUP::numGameItemsReplaced = 0; 960 int CUSTOMITEM_SETUP::numUserItemsReplaced = 0; 961 int CUSTOMITEM_SETUP::numNewItemsAdded = 0; 962 int CUSTOMITEM_SETUP::numFailedItems = 0; 963 char CUSTOMITEM_SETUP::showLog = 0; 964 965 void CUSTOMITEM_SETUP::Message(const char* pFormat, ...) 966 { 967 char buffer[512], *pBuf = buffer; 968 969 if (showLog) 970 { 971 pBuf += Bsprintf(pBuf, "ITEM SETUP:"); 972 pBuf += Bsprintf(pBuf, " "); 973 974 va_list args; 975 va_start(args, pFormat); 976 pBuf += vsprintf(pBuf, pFormat, args); 977 va_end(args); 978 979 pBuf += Bsprintf(pBuf, "\n"); 980 981 OSD_Printf("%s", buffer); 982 } 983 } 984 int CUSTOMITEM_SETUP::Setup(IniFile* pFile) 985 { 986 const char *p; char* pGroup = NULL; 987 ITEM *pItem, **pEntry; 988 989 dassert(pFile != NULL); 990 991 pDesc = pFile; 992 numNewItemsAdded = 0; 993 numGameItemsReplaced = 0; 994 numUserItemsReplaced = 0; 995 numFailedItems = 0; 996 997 if ((version = CheckVersion()) != kItemDescriptorVerMajor) 998 { 999 Message("Invalid item descriptor version, Given: %d, Req: %d", version, kItemDescriptorVerMajor); 1000 return 0; 1001 } 1002 1003 pFile->SectionRemove("General"); 1004 1005 while (pFile->GetNextSection(&pGroup)) 1006 { 1007 if ((pItem = Setup(pGroup)) == NULL) 1008 { 1009 numFailedItems++; 1010 continue; 1011 } 1012 pEntry = &gItems[pItem->type - kItemUserBase]; 1013 1014 if (*pEntry) 1015 { 1016 p = gItems[pItem->type - kItemUserBase]->name; 1017 Message("Custom item #%d (%s) has been replaced with \"%s\"!", pItem->type, p, pItem->name); 1018 ClearItem(*pEntry); numUserItemsReplaced++; 1019 } 1020 else if (rngok(pItem->type, kItemWeaponBase, kItemMax)) 1021 { 1022 p = GetGameItemName(pItem->type); 1023 Message("Game item #%d (%s) has been replaced with \"%s\"!", pItem->type, p, pItem->name); 1024 numGameItemsReplaced++; 1025 } 1026 else 1027 { 1028 numNewItemsAdded++; 1029 } 1030 1031 *pEntry = (ITEM*)malloc(sizeof(ITEM)); dassert(*pEntry != NULL); 1032 memcpy(*pEntry, pItem, sizeof(ITEM)); 1033 } 1034 1035 return numNewItemsAdded + numGameItemsReplaced + numUserItemsReplaced + numFailedItems; 1036 } 1037 1038 ITEM* CUSTOMITEM_SETUP::Setup(char* pGroup) 1039 { 1040 const char **pActOp, **pActType, *pKey, *pVal; 1041 int nPar, nType, nPrev = -1, i, j; 1042 char key[256], gotit = 0; 1043 1044 memset(&buffer, 0, sizeof(buffer)); 1045 1046 /** ITEM DEFAULTS **/ 1047 buffer.group = kItemGroupItem; 1048 buffer.appearance.xrepeat = 64; 1049 buffer.appearance.yrepeat = 64; 1050 buffer.appearance.shade = -8; 1051 1052 /* Get type and group before everything else. */ 1053 /*******************/ 1054 1055 pKey = gParItemMain[kParItemType]; 1056 pVal = pDesc->GetKeyString(pGroup, pKey); 1057 SetupType(pVal); 1058 1059 nType = buffer.type; 1060 if (!rngok(nType, kItemUserBase, kItemUserMax)) 1061 { 1062 Message("Item type %d is out of a range (%d-%d).", nType, kItemUserBase, kItemUserMax); 1063 return NULL; 1064 } 1065 1066 for (i = 0; i < LENGTH(gBannedItems); i++) 1067 { 1068 if (gBannedItems[i] != nType) 1069 continue; 1070 1071 Message("Game item #%d (%s) cannot be replaced.", nType, GetGameItemName(nType)); 1072 return NULL; 1073 } 1074 1075 /* Start ADDING a new custom item or FULLY REPLACING game one with it! */ 1076 /***************************************/ 1077 1078 SetupName(pGroup); 1079 1080 pKey = gParItemMain[kParItemFlags]; 1081 pVal = pDesc->GetKeyString(pGroup, pKey); 1082 SetupFlags(pVal); 1083 1084 buffer.appearance.sound = gItemGroupSnd[buffer.group]; 1085 1086 while (pDesc->GetNextString(&pKey, &pVal, &nPrev, pGroup)) 1087 { 1088 if (!pKey || !pVal) 1089 continue; 1090 1091 gotit = 0; 1092 1093 // Searching for normal params. 1094 if ((nPar = FindParam(pKey, gParItemMain)) >= 0) 1095 { 1096 switch (nPar) 1097 { 1098 case kParItemRespawn: SetupRespawn(pVal); break; 1099 case kParItemLiveTime: SetupLiveTime(pVal); break; 1100 case kParItemAppearance: SetupAppearance(pVal); break; 1101 case kParItemGameMode: SetupGameMode(pVal); break; 1102 case kParItemMessage: SetupMessage(pVal); break; 1103 } 1104 1105 continue; 1106 } 1107 1108 // Searching for prefixed params a.k.a actions. 1109 for (pActOp = gParItemActOperator, i = 0; *pActOp && !gotit; pActOp++, i++) 1110 { 1111 for (pActType = gParItemActType, j = 0; *pActType; pActType++, j++) 1112 { 1113 sprintf(key, "%s%s", *pActOp, *pActType); 1114 if (Bstrcasecmp(pKey, key) != 0) 1115 continue; 1116 1117 SetupAction(pVal, i, j); 1118 gotit = 1; 1119 break; 1120 } 1121 } 1122 } 1123 1124 if (buffer.numactions == 0 && buffer.numeffects > 0) 1125 { 1126 buffer.numactions = buffer.numeffects; 1127 buffer.numeffects = 0; 1128 1129 // Show no *default* pickup effect, 1130 // but allow to show user 1131 // ones. 1132 1133 buffer.flags |= kFlagItemNoEffect; 1134 } 1135 else if (buffer.numactions > 1 && buffer.numeffects > 0) 1136 { 1137 // Move effect actions to the bottom of list 1138 // so they won't appear when player pickups 1139 // nothing. 1140 1141 i = buffer.numactions + buffer.numeffects; 1142 qsort(buffer.action, i, sizeof(ITEM::ACTION), 1143 (int(*)(const void*, const void*))QsortActions); 1144 } 1145 1146 return &buffer; 1147 } 1148 1149 char CUSTOMITEM_SETUP::SetupAppearance(const char* str) 1150 { 1151 ITEM::APPEARANCE* pAppear = &buffer.appearance; 1152 int nPar, nVal, i = 0, c, t; 1153 char key[256], val[256]; 1154 1155 while ((i = enumStr(i, str, key, val)) != 0) 1156 { 1157 switch (nPar = FindParam(key, gParItemAppearEntry)) 1158 { 1159 case kParItemAppearSize: 1160 if (isarray(val, &t)) 1161 { 1162 t = c = 0; 1163 while ((t = enumStr(t, val, key)) != 0 && c < 2) 1164 { 1165 if (isufix(key) && (nVal = atoi(key)) <= 255) 1166 { 1167 if (c == 0) pAppear->xrepeat = nVal; 1168 else pAppear->yrepeat = nVal; 1169 } 1170 1171 c++; 1172 } 1173 } 1174 else if (isufix(val) && (nVal = atoi(val)) <= 255) 1175 { 1176 pAppear->xrepeat = pAppear->yrepeat = nVal; 1177 } 1178 break; 1179 case kParItemAppearTile: 1180 case kParItemAppearSeq: 1181 case kParItemAppearPal: 1182 case kParItemAppearShade: 1183 case kParItemAppearSnd: 1184 if (isfix(val)) 1185 { 1186 nVal = atoi(val); 1187 switch (nPar) 1188 { 1189 case kParItemAppearTile: if (rngok(nVal, 0, kMaxTiles)) pAppear->picnum = nVal; break; 1190 case kParItemAppearSeq: if (nVal >= 0) pAppear->seq = nVal; break; 1191 case kParItemAppearPal: if (irngok(nVal, 0, 255)) pAppear->pal = nVal; break; 1192 case kParItemAppearShade: if (irngok(nVal, -128, 127)) pAppear->shade = nVal; break; 1193 case kParItemAppearSnd: if (nVal >= 0) pAppear->sound = nVal; break; 1194 } 1195 } 1196 break; 1197 } 1198 } 1199 1200 return 1; 1201 } 1202 1203 char CUSTOMITEM_SETUP::SetupActionLimits(ITEM::ACTION* pAct, char extLimits) 1204 { 1205 switch (pAct->type) 1206 { 1207 case kItemActionHealth: 1208 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1209 pAct->amount[2] = SetMaxAmount(pAct->amount[2], (extLimits) ? 999 : 200); 1210 pAct->amount[0] <<= 4; 1211 pAct->amount[1] <<= 4; 1212 pAct->amount[2] <<= 4; 1213 break; 1214 case kItemActionArmor: 1215 if (pAct->slot < LENGTH(PLAYER::armor)) 1216 { 1217 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1218 pAct->amount[2] = SetMaxAmount(pAct->amount[2], (extLimits) ? 250 : 200); 1219 pAct->amount[0] <<= 4; 1220 pAct->amount[1] <<= 4; 1221 pAct->amount[2] <<= 4; 1222 break; 1223 } 1224 return 0; 1225 case kItemActionKey: 1226 if (++pAct->slot < LENGTH(PLAYER::hasKey)) 1227 { 1228 // This is bool 1229 pAct->amount[0] = (pAct->amount[0]) ? 1 : 0; 1230 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1231 pAct->amount[2] = SetMaxAmount(pAct->amount[2], 1); 1232 break; 1233 } 1234 return 0; 1235 case kItemActionWeapon: 1236 if (pAct->slot + 1 < LENGTH(PLAYER::hasWeapon)) 1237 { 1238 int n = gAmmoInfo[pAct->slot+1].max; 1239 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1240 if (extLimits) 1241 { 1242 if (n > 999) n = 9990; // spray can and such 1243 else if (n > 99) n = 999; // normal weapons 1244 else n = 99; // prox / remote bombs cannot have more than 2 digits in HUD. 1245 } 1246 1247 pAct->amount[2] = SetMaxAmount(pAct->amount[2], n); 1248 break; 1249 } 1250 return 0; 1251 case kItemActionAmmo: 1252 if (pAct->slot < LENGTH(PLAYER::ammoCount)) 1253 { 1254 int n = gAmmoInfo[pAct->slot].max; 1255 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1256 if (extLimits) 1257 { 1258 if (n > 999) n = 9990; // spray can and such 1259 else if (n > 99) n = 999; // normal weapons 1260 else n = 99; // prox / remote bombs cannot have more than 2 digits in HUD. 1261 } 1262 1263 pAct->amount[2] = SetMaxAmount(pAct->amount[2], n); 1264 break; 1265 } 1266 return 0; 1267 case kItemActionPowerTime: 1268 if (pAct->slot < LENGTH(PLAYER::pwUpTime)) 1269 { 1270 if (extLimits) 1271 { 1272 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1273 pAct->amount[2] = SetMaxAmount(pAct->amount[2], 999); 1274 pAct->amount[0] *= 100; 1275 pAct->amount[1] *= 100; 1276 pAct->amount[2] *= 100; 1277 } 1278 else 1279 { 1280 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1281 pAct->amount[2] = gPowerUpInfo[pAct->slot].maxTime; 1282 pAct->amount[0] *= 100; 1283 pAct->amount[1] *= 100; 1284 } 1285 1286 break; 1287 } 1288 return 0; 1289 case kItemActionPack: 1290 if (pAct->slot < LENGTH(PLAYER::packSlots)) 1291 { 1292 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1293 pAct->amount[2] = SetMaxAmount(pAct->amount[2], (extLimits) ? 999 : 100); 1294 break; 1295 } 1296 return 0; 1297 case kItemActionEffect: 1298 if (pAct->slot < kPlayerEffectMax) 1299 { 1300 break; 1301 } 1302 return 0; 1303 case kItemActionAirTime: 1304 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1305 pAct->amount[2] = SetMaxAmount(pAct->amount[2], 999); 1306 pAct->amount[0] *= 100; 1307 pAct->amount[1] *= 100; 1308 pAct->amount[2] *= 100; 1309 break; 1310 case kItemActionDmgIgnore: 1311 if (pAct->slot < kDamageMax) 1312 { 1313 // Can only iterate by 1 1314 pAct->amount[0] = (pAct->amount[0]) ? 1 : 0; 1315 pAct->amount[1] = SetMinAmount(pAct->amount[1], 0); 1316 break; 1317 } 1318 return 0; 1319 } 1320 1321 if (pAct->amount[1] > pAct->amount[2]) 1322 { 1323 int32_t t = pAct->amount[2]; 1324 pAct->amount[2] = pAct->amount[1]; 1325 pAct->amount[1] = t; 1326 } 1327 1328 switch (pAct->operation) 1329 { 1330 case kParItemActionAdd: 1331 if (pAct->amount[0] < 0) 1332 { 1333 pAct->amount[0] = klabs(pAct->amount[0]); 1334 pAct->operation = kParItemActionSub; 1335 } 1336 break; 1337 case kParItemActionSub: 1338 if (pAct->amount[0] < 0) 1339 { 1340 pAct->amount[0] = klabs(pAct->amount[0]); 1341 } 1342 break; 1343 } 1344 1345 return 1; 1346 } 1347 1348 char CUSTOMITEM_SETUP::SetupAction(const char* str, int nOperator, int nAction) 1349 { 1350 int nPar, nVal, i = 0; ITEM::ACTION action; 1351 char isEffect, key[256], val[256]; 1352 1353 memset(&action, 0, sizeof(action)); 1354 1355 action.amount[0] = 1; 1356 action.amount[1] = INT32_MIN + 1; 1357 action.amount[2] = INT32_MAX - 1; 1358 action.operation = nOperator; 1359 action.type = nAction; 1360 1361 while ((i = enumStr(i, str, key, val)) != 0) 1362 { 1363 switch (nPar = FindParam(key, gParItemActEntry)) 1364 { 1365 case kParActionAmount: 1366 case kParActionAmountMin: 1367 case kParActionAmountMax: 1368 if (isfix(val)) 1369 { 1370 nVal = atoi(val); 1371 1372 switch (nPar) 1373 { 1374 case kParActionAmount: action.amount[0] = nVal; break; 1375 case kParActionAmountMin: action.amount[1] = nVal; break; 1376 case kParActionAmountMax: action.amount[2] = nVal; break; 1377 } 1378 } 1379 break; 1380 case kParActionSlot: 1381 if (isufix(val)) 1382 { 1383 action.slot = atoi(val) - 1; 1384 } 1385 break; 1386 case kParActionReq: 1387 if ((nVal = btoi(val)) > 0) 1388 { 1389 action.flags |= kFlagActionReq; 1390 } 1391 break; 1392 case kParActionCompat: 1393 if ((nVal = btoi(val)) > 0) 1394 { 1395 action.flags |= kFlagActionCompat; 1396 } 1397 break; 1398 } 1399 } 1400 1401 if (SetupActionLimits(&action, (buffer.flags & kFlagItemExtLimits) != 0)) 1402 { 1403 isEffect = (action.type == kItemActionEffect); 1404 1405 if (isEffect && (buffer.flags & kFlagItemNoEffect)) 1406 return 1; 1407 1408 if ((i = buffer.numactions + buffer.numeffects) < 255) 1409 { 1410 if (action.flags & kFlagActionReq) 1411 buffer.flags |= kFlagItemHasReqActions; 1412 1413 buffer.action = (ITEM::ACTION*)realloc(buffer.action, sizeof(action) * (i + 1)); dassert(buffer.action != NULL); 1414 memcpy(&buffer.action[i], &action, sizeof(action)); 1415 1416 if (isEffect) buffer.numeffects++; 1417 else buffer.numactions++; 1418 1419 return 1; 1420 } 1421 } 1422 1423 return 0; 1424 } 1425 1426 char CUSTOMITEM_SETUP::SetupType(const char* str) 1427 { 1428 int nPar, i = 0, t; 1429 char val[256]; 1430 1431 if (isarray(str, &t)) 1432 { 1433 if ((i = enumStr(i, str, val)) != 0) 1434 { 1435 if ((nPar = FindParam(val, gParItemGroup)) >= 0) 1436 buffer.group = nPar, t--; 1437 } 1438 1439 if ((i = enumStr(i, str, val)) != 0) 1440 { 1441 if (isufix(val)) 1442 buffer.type = atoi(val), t--; 1443 } 1444 1445 return (t == 0); 1446 } 1447 else if (isufix(str)) 1448 { 1449 buffer.group = kItemGroupItem; 1450 buffer.type = atoi(str); 1451 return 1; 1452 } 1453 1454 return 0; 1455 } 1456 1457 char CUSTOMITEM_SETUP::SetupRespawn(const char* str) 1458 { 1459 int nPar; 1460 1461 if (isufix(str)) 1462 { 1463 buffer.respawntime = (uint8_t)ClipHigh(atoi(str), 255); 1464 return 1; 1465 } 1466 else if ((nPar = FindParam(str, gParRespawnTime)) >= 0) 1467 { 1468 switch (nPar) 1469 { 1470 case kRespawnTimeSpecial1: 1471 buffer.flags |= kFlagItemRespawnSpec1; 1472 buffer.respawntime = 0; 1473 return 1; 1474 case kRespawnTimeSpecial2: 1475 buffer.flags |= kFlagItemRespawnSpec2; 1476 buffer.respawntime = 0; 1477 return 1; 1478 } 1479 } 1480 1481 return 0; 1482 } 1483 1484 char CUSTOMITEM_SETUP::SetupLiveTime(const char* str) 1485 { 1486 if (isufix(str)) 1487 { 1488 buffer.droplivetime = (uint8_t)ClipHigh(atoi(str), 255); 1489 return 1; 1490 } 1491 1492 return 0; 1493 } 1494 1495 char CUSTOMITEM_SETUP::SetupName(const char* str) 1496 { 1497 const char* p; char tmp[32]; 1498 int l; 1499 1500 l = ((p = strchr(str, '_')) != NULL) ? (p - str) : strlen(str); 1501 1502 if (l <= 0) 1503 { 1504 l = sprintf(tmp, "Unnamed#%d", buffer.type); 1505 str = tmp; 1506 } 1507 1508 buffer.name = (char*)malloc(l + 1); dassert(buffer.name != NULL); 1509 strncpy(buffer.name, str, l); 1510 buffer.name[l] = '\0'; 1511 return 1; 1512 } 1513 1514 char CUSTOMITEM_SETUP::SetupMessage(const char* str) 1515 { 1516 const char* p = str; 1517 int n = strlen(str); 1518 int i = 0; 1519 1520 1521 if ((buffer.flags & kFlagItemNoMessage) == 0) 1522 { 1523 while ((p = strchr(p, '%')) != NULL && i < 2) 1524 { 1525 p++; 1526 if (*p == '%') 1527 { 1528 p++; 1529 continue; 1530 } 1531 1532 if (*p != 's') 1533 { 1534 i = 2; 1535 break; 1536 } 1537 1538 i++; 1539 } 1540 1541 if (i <= 1) 1542 { 1543 if (i) 1544 n += strlen(buffer.name); 1545 1546 buffer.message = (char*)malloc(n + 1); dassert(buffer.message != NULL); 1547 1548 if (i) sprintf(buffer.message, str, buffer.name); 1549 else strcpy(buffer.message, str); 1550 1551 return 1; 1552 } 1553 1554 return 0; 1555 } 1556 1557 return 1; 1558 } 1559 1560 char CUSTOMITEM_SETUP::SetupGameMode(const char* str) 1561 { 1562 int nPar, i = 0; char val[256]; 1563 int16_t nFlags; 1564 1565 nFlags = (kFlagItemNoPickupS | kFlagItemNoPickupB | kFlagItemNoPickupC | kFlagItemNoPickupT); 1566 1567 while ((i = enumStr(i, str, val)) != 0) 1568 { 1569 switch (nPar = FindParam(val, gParItemGametype)) 1570 { 1571 case kParItemGameS: nFlags &= ~kFlagItemNoPickupS; break; 1572 case kParItemGameB: nFlags &= ~kFlagItemNoPickupB; break; 1573 case kParItemGameC: nFlags &= ~kFlagItemNoPickupC; break; 1574 case kParItemGameT: nFlags &= ~kFlagItemNoPickupT; break; 1575 } 1576 } 1577 1578 buffer.flags ^= nFlags; 1579 return 1; 1580 } 1581 1582 char CUSTOMITEM_SETUP::SetupFlags(const char* str) 1583 { 1584 int nPar, i = 0; char val[256]; 1585 while ((i = enumStr(i, str, val)) != 0) 1586 { 1587 switch (nPar = FindParam(val, gParItemFlags)) 1588 { 1589 case kParItemFlagsNoMsg: buffer.flags |= kFlagItemNoMessage; break; 1590 case kParItemFlagsNoEff: buffer.flags |= kFlagItemNoEffect; break; 1591 case kParItemFlagsShared: buffer.flags |= kFlagItemShared; break; 1592 case kParItemFlagsExtLimits: buffer.flags |= kFlagItemExtLimits; break; 1593 } 1594 } 1595 1596 return 1; 1597 } 1598 1599 int CUSTOMITEM_SETUP::CheckVersion(void) 1600 { 1601 int nRetn = 0; char major[16]; 1602 const char* pValue = NULL; 1603 1604 if (pDesc) 1605 { 1606 if (!pDesc->SectionExists("General")) 1607 { 1608 Message("Section \"General\" not found!"); 1609 return nRetn; 1610 } 1611 1612 pValue = pDesc->GetKeyString("General", "Version"); 1613 if (pValue && rngok(Bstrlen(pValue), 0, 5)) 1614 { 1615 major[0] = pValue[0]; major[1] = '\0'; 1616 if (isdigit(major[0])) 1617 nRetn = atoi(major); 1618 } 1619 } 1620 1621 return nRetn; 1622 } 1623 1624 void CUSTOMITEM_SETUP::ClearItem(ITEM* pItem) 1625 { 1626 if (pItem->action) 1627 free(pItem->action), pItem->action = NULL; 1628 1629 if (pItem->name) 1630 free(pItem->name), pItem->name = NULL; 1631 1632 if (pItem->message) 1633 free(pItem->message), pItem->message = NULL; 1634 1635 pItem->numactions = 0; 1636 pItem->numeffects = 0; 1637 } 1638 1639 int CUSTOMITEM_SETUP::QsortActions(ITEM::ACTION* ref1, ITEM::ACTION* ref2) 1640 { 1641 UNREFERENCED_PARAMETER(ref2); 1642 if (ref1->type == kItemActionEffect) 1643 return 1; 1644 1645 return 0; 1646 } 1647 1648 int CUSTOMITEM_SETUP::FindParam(const char* str, const char** pDb) 1649 { 1650 const char** p; int n; 1651 for (p = pDb, n = 0; *p; p++, n++) 1652 if (Bstrcasecmp(str, *p) == 0) 1653 return n; 1654 1655 return -1; 1656 } 1657 1658 int CUSTOMITEM_SETUP::SetMaxAmount(int nAmount, int nMax) 1659 { 1660 //if (nAmount) 1661 return ClipHigh(nAmount, nMax); 1662 1663 //return nMax; 1664 } 1665 1666 int CUSTOMITEM_SETUP::SetMinAmount(int nAmount, int nMin) 1667 { 1668 //if (nAmount) 1669 return ClipLow(nAmount, nMin); 1670 1671 //return nMin; 1672 } 1673 1674 const char* CUSTOMITEM_SETUP::GetGameItemName(int nType) 1675 { 1676 const char* p = NULL; 1677 1678 if (rngok(nType, kItemWeaponBase, kItemWeaponMax)) p = gWeaponText[nType - kItemWeaponBase]; 1679 else if (rngok(nType, kItemAmmoBase, kItemAmmoMax)) p = gAmmoText[nType - kItemAmmoBase]; 1680 else if (rngok(nType, kItemBase, kItemMax)) p = gItemText[nType - kItemBase]; 1681 1682 if (isempty(p)) 1683 p = "Unnamed"; 1684 1685 return p; 1686 } 1687 #endif