/ source / blood / src / nnextcitem.cpp
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