@@ -33,6 +33,9 @@ std::map<int32_t, int32_t> Items::EventToDropMap;
3333std::map<int32_t , int32_t > Items::MobToDropMap;
3434std::map<int32_t , ItemSet> Items::ItemSets;
3535
36+ // 1 week
37+ #define NANOCOM_BOOSTER_DURATION 604800
38+
3639#ifdef ACADEMY
3740std::map<int32_t , int32_t > Items::NanoCapsules; // crate id -> nano id
3841
@@ -502,7 +505,81 @@ static void useGumball(CNSocket* sock, CNPacketData* data) {
502505}
503506
504507static void useNanocomBooster (CNSocket* sock, CNPacketData* data) {
508+ // Guard against using nanocom boosters in before and including 0104
509+ // either path should be optimized by the compiler, effectively a no-op
510+ if (AEQUIP_COUNT < 12 ) {
511+ std::cout << " [WARN] Nanocom Booster use not supported in this version" << std::endl;
512+ INITSTRUCT (sP_FE2CL_REP_PC_ITEM_USE_FAIL , respFail);
513+ sock->sendPacket (respFail, P_FE2CL_REP_PC_ITEM_USE_FAIL);
514+ return ;
515+ }
516+
517+ auto request = (sP_CL2FE_REQ_ITEM_USE *)data->buf ;
518+ Player* player = PlayerManager::getPlayer (sock);
519+ sItemBase item = player->Inven [request->iSlotNum ];
520+
521+ // consume item
522+ item.iOpt -= 1 ;
523+ if (item.iOpt == 0 )
524+ item = {};
525+
526+ // decide on the booster to activate
527+ std::vector<int16_t > boosterIDs;
528+ switch (item.iID ) {
529+ case 153 :
530+ case 154 :
531+ case 155 :
532+ boosterIDs.push_back (item.iID );
533+ break ;
534+ case 156 :
535+ boosterIDs.push_back (153 );
536+ boosterIDs.push_back (154 );
537+ boosterIDs.push_back (155 );
538+ break ;
539+ }
540+
541+ // client wants to subtract server time in seconds from the time limit for display purposes
542+ int32_t timeLimitDisplayed = (getTime () / 1000UL ) + NANOCOM_BOOSTER_DURATION;
543+ // in actuality we will use the timestamp of booster activation to the item time limit similar to vehicles
544+ // and this is how it will be saved to the database
545+ int32_t timeLimit = getTimestamp () + NANOCOM_BOOSTER_DURATION;
546+
547+ // give item(s) to inv slots
548+ for (int16_t itemID : boosterIDs) {
549+ sItemBase boosterItem = { 7 , itemID, 1 , timeLimitDisplayed };
550+
551+ // 155 -> 9, 153 -> 10, 154 -> 11
552+ int slot = 9 + ((itemID - 152 ) % 3 );
553+
554+ // give item to the equip slot
555+ INITSTRUCT (sP_FE2CL_REP_PC_GIVE_ITEM_SUCC , resp);
556+ resp.eIL = (int )SlotType::EQUIP;
557+ resp.iSlotNum = slot;
558+ resp.Item = boosterItem;
559+ sock->sendPacket (resp, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
560+
561+ // inform client of equip change (non visible so it's okay to just send to the player)
562+ INITSTRUCT (sP_FE2CL_PC_EQUIP_CHANGE , equipChange);
563+ equipChange.iPC_ID = player->iID ;
564+ equipChange.iEquipSlotNum = slot;
565+ equipChange.EquipSlotItem = boosterItem;
566+ sock->sendPacket (equipChange, P_FE2CL_PC_EQUIP_CHANGE);
567+
568+ boosterItem.iTimeLimit = timeLimit;
569+ // should replace existing booster in slot if it exists, i.e. you can refresh your boosters
570+ player->Equip [slot] = boosterItem;
571+ }
572+
573+ // send item use success packet
574+ INITSTRUCT (sP_FE2CL_REP_PC_ITEM_USE_SUCC , respUse);
575+ respUse.iPC_ID = player->iID ;
576+ respUse.eIL = (int )SlotType::INVENTORY;
577+ respUse.iSlotNum = request->iSlotNum ;
578+ respUse.RemainItem = item;
579+ sock->sendPacket (respUse, P_FE2CL_REP_PC_ITEM_USE_SUCC);
505580
581+ // update inventory serverside
582+ player->Inven [request->iSlotNum ] = item;
506583}
507584
508585static void itemUseHandler (CNSocket* sock, CNPacketData* data) {
@@ -666,39 +743,87 @@ Item* Items::getItemData(int32_t id, int32_t type) {
666743 return nullptr ;
667744}
668745
669- void Items::checkItemExpire (CNSocket* sock, Player* player) {
670- if (player->expiringItem .eIL == 0 && player->expiringItem .iSlotNum == 0 )
671- return ;
746+ size_t Items::checkAndRemoveExpiredItems (CNSocket* sock, Player* player) {
747+ int32_t currentTime = getTimestamp ();
748+
749+ // if there are expired items in bank just remove them silently
750+ for (int i = 0 ; i < ABANK_COUNT; i++) {
751+ if (player->Bank [i].iTimeLimit < currentTime && player->Bank [i].iTimeLimit != 0 ) {
752+ memset (&player->Bank [i], 0 , sizeof (sItemBase ));
753+ }
754+ }
755+
756+ // collect items to remove and data for the packet
757+ std::vector<sItemBase *> toRemove;
758+ std::vector<sTimeLimitItemDeleteInfo2CL > itemData;
759+
760+ // equipped items
761+ for (int i = 0 ; i < AEQUIP_COUNT; i++) {
762+ if (player->Equip [i].iOpt > 0 && player->Equip [i].iTimeLimit < currentTime && player->Equip [i].iTimeLimit != 0 ) {
763+ toRemove.push_back (&player->Equip [i]);
764+ itemData.push_back ({ (int )SlotType::EQUIP, i });
765+ }
766+ }
767+ // inventory
768+ for (int i = 0 ; i < AINVEN_COUNT; i++) {
769+ if (player->Inven [i].iTimeLimit < currentTime && player->Inven [i].iTimeLimit != 0 ) {
770+ toRemove.push_back (&player->Inven [i]);
771+ itemData.push_back ({ (int )SlotType::INVENTORY, i });
772+ }
773+ }
774+
775+ if (itemData.empty ())
776+ return 0 ;
672777
673- /* prepare packet
674- * yes, this is a varadic packet, however analyzing client behavior and code
675- * it only checks takes the first item sent into account
676- * yes, this is very stupid
677- * therefore, we delete all but 1 expired item while loading player
678- * to delete the last one here so player gets a notification
679- */
778+ // prepare packet containing all expired items to delete
779+ // this is expected for academy
780+ // pre-academy only checks the first item in the packet
781+ const size_t resplen = sizeof (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM ) + sizeof (sTimeLimitItemDeleteInfo2CL ) * itemData.size ();
680782
681- const size_t resplen = sizeof ( sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM ) + sizeof ( sTimeLimitItemDeleteInfo2CL );
783+ // 8 bytes * 262 items = 2096 bytes, in total this shouldn't exceed 2500 bytes
682784 assert (resplen < CN_PACKET_BODY_SIZE);
683- // we know it's only one trailing struct, so we can skip full validation
684- uint8_t respbuf[resplen]; // not a variable length array, don't worry
785+ uint8_t respbuf[CN_PACKET_BODY_SIZE];
786+ memset (respbuf, 0 , CN_PACKET_BODY_SIZE);
787+
685788 auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM *)respbuf;
686- sTimeLimitItemDeleteInfo2CL * itemData = (sTimeLimitItemDeleteInfo2CL *)(respbuf + sizeof (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM ));
687- memset (respbuf, 0 , resplen);
688789
689- packet->iItemListCount = 1 ;
690- itemData->eIL = player->expiringItem .eIL ;
691- itemData->iSlotNum = player->expiringItem .iSlotNum ;
790+ for (size_t i = 0 ; i < itemData.size (); i++) {
791+ auto itemToDeletePtr = (sTimeLimitItemDeleteInfo2CL *)(
792+ respbuf + sizeof (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM ) + sizeof (sTimeLimitItemDeleteInfo2CL ) * i
793+ );
794+ itemToDeletePtr->eIL = itemData[i].eIL ;
795+ itemToDeletePtr->iSlotNum = itemData[i].iSlotNum ;
796+ packet->iItemListCount ++;
797+ }
798+
692799 sock->sendPacket ((void *)&respbuf, P_FE2CL_PC_DELETE_TIME_LIMIT_ITEM, resplen);
693800
694- // delete serverside
695- if (player->expiringItem .eIL == 0 )
696- memset (&player->Equip [player->expiringItem .iSlotNum ], 0 , sizeof (sItemBase ));
697- else
698- memset (&player->Inven [player->expiringItem .iSlotNum ], 0 , sizeof (sItemBase ));
801+ // delete items serverside and send unequip packets
802+ for (size_t i = 0 ; i < itemData.size (); i++) {
803+ sItemBase * item = toRemove[i];
804+ memset (item, 0 , sizeof (sItemBase ));
805+
806+ // send item delete success packet
807+ // required for pre-academy builds
808+ INITSTRUCT (sP_FE2CL_REP_PC_ITEM_DELETE_SUCC , itemDelete);
809+ itemDelete.eIL = itemData[i].eIL ;
810+ itemDelete.iSlotNum = itemData[i].iSlotNum ;
811+ sock->sendPacket (itemDelete, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
812+
813+ // also update item equips if needed
814+ if (itemData[i].eIL == (int )SlotType::EQUIP) {
815+ INITSTRUCT (sP_FE2CL_PC_EQUIP_CHANGE , equipChange);
816+ equipChange.iPC_ID = player->iID ;
817+ equipChange.iEquipSlotNum = itemData[i].iSlotNum ;
818+ sock->sendPacket (equipChange, P_FE2CL_PC_EQUIP_CHANGE);
819+ }
820+ }
821+
822+ // exit vehicle if player no longer has one equipped (function checks pcstyle)
823+ if (player->Equip [8 ].iID == 0 )
824+ PlayerManager::exitPlayerVehicle (sock, nullptr );
699825
700- player->expiringItem .eIL = 0 ;
701- player->expiringItem .iSlotNum = 0 ;
826+ return itemData.size ();
702827}
703828
704829void Items::setItemStats (Player* plr) {
0 commit comments