/** * @brief Conditions for moving items between containers. * @param[in] self The inventory interface pointer * @param[in] inv Inventory to move in. * @param[in] from Source container. * @param[in] fItem The item to be moved. * @param[in] to Destination container. * @param[in] tx X coordinate in destination container. * @param[in] ty Y coordinate in destination container. * @param[in,out] TU pointer to entity available TU at this moment * or @c NULL if TU doesn't matter (outside battlescape) * @param[out] icp * @return IA_NOTIME when not enough TU. * @return IA_NONE if no action possible. * @return IA_NORELOAD if you cannot reload a weapon. * @return IA_RELOAD_SWAP in case of exchange of ammo in a weapon. * @return IA_RELOAD when reloading. * @return IA_ARMOUR when placing an armour on the actor. * @return IA_MOVE when just moving an item. */ static int I_MoveInInventory (inventoryInterface_t* self, inventory_t* const inv, const invDef_t * from, invList_t *fItem, const invDef_t * to, int tx, int ty, int *TU, invList_t ** icp) { invList_t *ic; int time; int checkedTo = INV_DOES_NOT_FIT; qboolean alreadyRemovedSource = qfalse; assert(to); assert(from); if (icp) *icp = NULL; if (from == to && fItem->x == tx && fItem->y == ty) return IA_NONE; time = from->out + to->in; if (from == to) { if (INV_IsFloorDef(from)) time = 0; else time /= 2; } if (TU && *TU < time) return IA_NOTIME; assert(inv); /* Special case for moving an item within the same container. */ if (from == to) { /* Do nothing if we move inside a scroll container. */ if (from->scroll) return IA_NONE; ic = inv->c[from->id]; for (; ic; ic = ic->next) { if (ic == fItem) { if (ic->item.amount > 1) { checkedTo = INVSH_CheckToInventory(inv, ic->item.t, to, tx, ty, fItem); if (checkedTo & INV_FITS) { ic->x = tx; ic->y = ty; if (icp) *icp = ic; return IA_MOVE; } return IA_NONE; } } } } /* If weapon is twohanded and is moved from hand to hand do nothing. */ /* Twohanded weapon are only in CSI->idRight. */ if (fItem->item.t->fireTwoHanded && INV_IsLeftDef(to) && INV_IsRightDef(from)) { return IA_NONE; } /* If non-armour moved to an armour slot then abort. * Same for non extension items when moved to an extension slot. */ if ((to->armour && !INV_IsArmour(fItem->item.t)) || (to->extension && !fItem->item.t->extension) || (to->headgear && !fItem->item.t->headgear)) { return IA_NONE; } /* Check if the target is a blocked inv-armour and source!=dest. */ if (to->single) checkedTo = INVSH_CheckToInventory(inv, fItem->item.t, to, 0, 0, fItem); else { if (tx == NONE || ty == NONE) INVSH_FindSpace(inv, &fItem->item, to, &tx, &ty, fItem); /* still no valid location found */ if (tx == NONE || ty == NONE) return IA_NONE; checkedTo = INVSH_CheckToInventory(inv, fItem->item.t, to, tx, ty, fItem); } if (to->armour && from != to && !checkedTo) { item_t cacheItem2; invList_t *icTo; /* Store x/y origin coordinates of removed (source) item. * When we re-add it we can use this. */ const int cacheFromX = fItem->x; const int cacheFromY = fItem->y; /* Check if destination/blocking item is the same as source/from item. * In that case the move is not needed -> abort. */ icTo = INVSH_SearchInInventory(inv, to, tx, ty); if (fItem->item.t == icTo->item.t) return IA_NONE; /* Actually remove the ammo from the 'from' container. */ if (!self->RemoveFromInventory(self, inv, from, fItem)) return IA_NONE; else /* Removal successful - store this info. */ alreadyRemovedSource = qtrue; cacheItem2 = self->cacheItem; /* Save/cache (source) item. The cacheItem is modified in I_MoveInInventory. */ /* Move the destination item to the source. */ self->MoveInInventory(self, inv, to, icTo, from, cacheFromX, cacheFromY, TU, icp); /* Reset the cached item (source) (It'll be move to container emptied by destination item later.) */ self->cacheItem = cacheItem2; } else if (!checkedTo) { /* Get the target-invlist (e.g. a weapon). We don't need to check for * scroll because checkedTo is always true here. */ ic = INVSH_SearchInInventory(inv, to, tx, ty); if (ic && !INV_IsEquipDef(to) && INVSH_LoadableInWeapon(fItem->item.t, ic->item.t)) { /* A target-item was found and the dragged item (implicitly ammo) * can be loaded in it (implicitly weapon). */ if (ic->item.a >= ic->item.t->ammo && ic->item.m == fItem->item.t) { /* Weapon already fully loaded with the same ammunition -> abort */ return IA_NORELOAD; } time += ic->item.t->reload; if (!TU || *TU >= time) { if (TU) *TU -= time; if (ic->item.a >= ic->item.t->ammo) { /* exchange ammo */ const item_t item = {NONE_AMMO, NULL, ic->item.m, 0, 0}; /* Put current ammo in place of the new ammo unless floor - there can be more than 1 item */ const int cacheFromX = INV_IsFloorDef(from) ? NONE : fItem->x; const int cacheFromY = INV_IsFloorDef(from) ? NONE : fItem->y; /* Actually remove the ammo from the 'from' container. */ if (!self->RemoveFromInventory(self, inv, from, fItem)) return IA_NONE; /* Add the currently used ammo in place of the new ammo in the "from" container. */ if (self->AddToInventory(self, inv, &item, from, cacheFromX, cacheFromY, 1) == NULL) Sys_Error("Could not reload the weapon - add to inventory failed (%s)", self->name); ic->item.m = self->cacheItem.t; if (icp) *icp = ic; return IA_RELOAD_SWAP; } else { /* Actually remove the ammo from the 'from' container. */ if (!self->RemoveFromInventory(self, inv, from, fItem)) return IA_NONE; ic->item.m = self->cacheItem.t; /* loose ammo of type ic->item.m saved on server side */ ic->item.a = ic->item.t->ammo; if (icp) *icp = ic; return IA_RELOAD; } } /* Not enough time -> abort. */ return IA_NOTIME; } /* temp container like idEquip and idFloor */ if (ic && to->temp) { /* We are moving to a blocked location container but it's the base-equipment floor or a battlescape floor. * We add the item anyway but it'll not be displayed (yet) * This is then used in I_AddToInventory below.*/ /** @todo change the other code to browse trough these things. */ INVSH_FindSpace(inv, &fItem->item, to, &tx, &ty, fItem); if (tx == NONE || ty == NONE) { Com_DPrintf(DEBUG_SHARED, "I_MoveInInventory - item will be added non-visible (%s)\n", self->name); } } else { /* Impossible move -> abort. */ return IA_NONE; } } /* twohanded exception - only CSI->idRight is allowed for fireTwoHanded weapons */ if (fItem->item.t->fireTwoHanded && INV_IsLeftDef(to)) to = &self->csi->ids[self->csi->idRight]; if (checkedTo == INV_FITS_ONLY_ROTATED) { /* Set rotated tag */ fItem->item.rotated = qtrue; } else if (fItem->item.rotated) { /* Remove rotated tag */ fItem->item.rotated = qfalse; } /* Actually remove the item from the 'from' container (if it wasn't already removed). */ if (!alreadyRemovedSource) if (!self->RemoveFromInventory(self, inv, from, fItem)) return IA_NONE; /* successful */ if (TU) *TU -= time; assert(self->cacheItem.t); ic = self->AddToInventory(self, inv, &self->cacheItem, to, tx, ty, 1); /* return data */ if (icp) { assert(ic); *icp = ic; } if (INV_IsArmourDef(to)) { assert(INV_IsArmour(self->cacheItem.t)); return IA_ARMOUR; } else return IA_MOVE; }
/** * @brief Call into the target when the DND hover it * @return True if the DND is accepted */ bool uiContainerNode::onDndMove (uiNode_t *target, int x, int y) { vec2_t nodepos; bool exists; int itemX = 0; int itemY = 0; item_t *dragItem = UI_DNDGetItem(); /* we already check it when the node accept the DND */ assert(EXTRADATA(target).container); UI_GetNodeAbsPos(target, nodepos); /** We calculate the position of the top-left corner of the dragged * item in oder to compensate for the centered-drawn cursor-item. * Or to be more exact, we calculate the relative offset from the cursor * location to the middle of the top-left square of the item. * @sa UI_LeftClick */ if (dragItem->item) { itemX = C_UNIT * dragItem->item->sx / 2; /* Half item-width. */ itemY = C_UNIT * dragItem->item->sy / 2; /* Half item-height. */ /* Place relative center in the middle of the square. */ itemX -= C_UNIT / 2; itemY -= C_UNIT / 2; } dragInfoToX = (mousePosX - nodepos[0] - itemX) / C_UNIT; dragInfoToY = (mousePosY - nodepos[1] - itemY) / C_UNIT; /* Check if the items already exists in the container. i.e. there is already at least one item. */ exists = false; if ((INV_IsFloorDef(EXTRADATA(target).container) || INV_IsEquipDef(EXTRADATA(target).container)) && (dragInfoToX < 0 || dragInfoToY < 0 || dragInfoToX >= SHAPE_BIG_MAX_WIDTH || dragInfoToY >= SHAPE_BIG_MAX_HEIGHT) && INVSH_ExistsInInventory(ui_inventory, EXTRADATA(target).container, dragItem)) { exists = true; } /* Search for a suitable position to render the item at if * the container is "single", the cursor is out of bound of the container. */ if (!exists && dragItem->item && (EXTRADATA(target).container->single || dragInfoToX < 0 || dragInfoToY < 0 || dragInfoToX >= SHAPE_BIG_MAX_WIDTH || dragInfoToY >= SHAPE_BIG_MAX_HEIGHT)) { #if 0 /* ... or there is something in the way. */ /* We would need to check for weapon/ammo as well here, otherwise a preview would be drawn as well when hovering over the correct weapon to reload. */ || (INVSH_CheckToInventory(ui_inventory, dragItem->item, EXTRADATA(target).container, dragInfoToX, dragInfoToY) == INV_DOES_NOT_FIT)) { #endif INVSH_FindSpace(ui_inventory, dragItem, EXTRADATA(target).container, &dragInfoToX, &dragInfoToY, dragInfoIC); } /* we can drag every thing */ if (UI_IsScrollContainerNode(target)) { return true; } { invList_t *fItem; /* is there empty slot? */ const int checkedTo = INVSH_CheckToInventory(ui_inventory, dragItem->item, EXTRADATA(target).container, dragInfoToX, dragInfoToY, dragInfoIC); if (checkedTo != INV_DOES_NOT_FIT) return true; /* can we equip dragging item into the target item? */ fItem = INVSH_SearchInInventory(ui_inventory, EXTRADATA(target).container, dragInfoToX, dragInfoToY); if (!fItem) return false; if (EXTRADATA(target).container->single) return true; return INVSH_LoadableInWeapon(dragItem->item, fItem->item.item); } } /** * @brief Call when a DND enter into the node */ void uiContainerNode::onDndLeave (uiNode_t *node) { dragInfoToX = -1; dragInfoToY = -1; }