/** * @brief Draws the free and usable inventory positions when dragging an item. * @note Only call this function in dragging mode */ static void UI_ContainerNodeDrawFreeSpace (uiNode_t *node, inventory_t *inv) { const objDef_t *od = UI_DNDGetItem()->item; /**< Get the 'type' of the dragged item. */ vec2_t nodepos; /* Draw only in dragging-mode and not for the equip-floor */ assert(UI_DNDIsDragging()); assert(inv); UI_GetNodeAbsPos(node, nodepos); /* if single container (hands, extension, headgear) */ if (EXTRADATA(node).container->single) { /* if container is free or the dragged-item is in it */ if (UI_DNDIsSourceNode(node) || INVSH_CheckToInventory(inv, od, EXTRADATA(node).container, 0, 0, dragInfoIC)) UI_DrawFree(EXTRADATA(node).container->id, node, nodepos[0], nodepos[1], node->box.size[0], node->box.size[1], true); } else { /* The shape of the free positions. */ uint32_t free[SHAPE_BIG_MAX_HEIGHT]; bool showTUs = true; int x, y; OBJZERO(free); for (y = 0; y < SHAPE_BIG_MAX_HEIGHT; y++) { for (x = 0; x < SHAPE_BIG_MAX_WIDTH; x++) { /* Check if the current position is usable (topleft of the item). */ /* Add '1's to each position the item is 'blocking'. */ const int checkedTo = INVSH_CheckToInventory(inv, od, EXTRADATA(node).container, x, y, dragInfoIC); if (checkedTo & INV_FITS) /* Item can be placed normally. */ INVSH_MergeShapes(free, (uint32_t)od->shape, x, y); if (checkedTo & INV_FITS_ONLY_ROTATED) /* Item can be placed rotated. */ INVSH_MergeShapes(free, INVSH_ShapeRotate((uint32_t)od->shape), x, y); /* Only draw on existing positions. */ if (INVSH_CheckShape(EXTRADATA(node).container->shape, x, y)) { if (INVSH_CheckShape(free, x, y)) { UI_DrawFree(EXTRADATA(node).container->id, node, nodepos[0] + x * C_UNIT, nodepos[1] + y * C_UNIT, C_UNIT, C_UNIT, showTUs); showTUs = false; } } } } } }
/** * @brief Add an item to a specified container in a given inventory. * @note Set x and y to NONE if the item should get added to an automatically chosen free spot in the container. * @param[in] self The inventory interface pointer * @param[in,out] inv Pointer to inventory definition, to which we will add item. * @param[in] item Item to add to given container (needs to have "rotated" tag already set/checked, this is NOT checked here!) * @param[in] container Container in given inventory definition, where the new item will be stored. * @param[in] x The x location in the container. * @param[in] y The x location in the container. * @param[in] amount How many items of this type should be added. (this will overwrite the amount as defined in "item.amount") * @sa I_RemoveFromInventory * @return the @c invList_t pointer the item was added to, or @c NULL in case of an error (item wasn't added) */ static invList_t *I_AddToInventory (inventoryInterface_t* self, inventory_t * const inv, const item_t* const item, const invDef_t * container, int x, int y, int amount) { invList_t *ic; int checkedTo; if (!item->t) return NULL; if (amount <= 0) return NULL; assert(inv); assert(container); if (container->single && inv->c[container->id] && inv->c[container->id]->next) return NULL; /* idEquip and idFloor */ if (container->temp) { for (ic = inv->c[container->id]; ic; ic = ic->next) if (INVSH_CompareItem(&ic->item, item)) { ic->item.amount += amount; Com_DPrintf(DEBUG_SHARED, "I_AddToInventory: Amount of '%s': %i (%s)\n", ic->item.t->name, ic->item.amount, self->name); return ic; } } if (x < 0 || y < 0 || x >= SHAPE_BIG_MAX_WIDTH || y >= SHAPE_BIG_MAX_HEIGHT) { /* No (sane) position in container given as parameter - find free space on our own. */ INVSH_FindSpace(inv, item, container, &x, &y, NULL); if (x == NONE) return NULL; } checkedTo = INVSH_CheckToInventory(inv, item->t, container, x, y, NULL); assert(checkedTo); /* not found - add a new one */ ic = I_AddInvList(self, &inv->c[container->id]); /* Set the data in the new entry to the data we got via function-parameters.*/ ic->item = *item; ic->item.amount = amount; /* don't reset an already applied rotation */ if (checkedTo == INV_FITS_ONLY_ROTATED) ic->item.rotated = qtrue; ic->x = x; ic->y = y; return ic; }
/** * @brief Draw a preview of the DND item dropped into the node */ static void UI_ContainerNodeDrawDropPreview (uiNode_t *target) { item_t previewItem; int checkedTo; vec3_t origine; /* no preview into scrollable list */ if (UI_IsScrollContainerNode(target)) return; /* copy the DND item to not change the original one */ previewItem = *UI_DNDGetItem(); previewItem.rotated = false; checkedTo = INVSH_CheckToInventory(ui_inventory, previewItem.item, EXTRADATA(target).container, dragInfoToX, dragInfoToY, dragInfoIC); switch (checkedTo) { case INV_DOES_NOT_FIT: return; case INV_FITS: break; case INV_FITS_BOTH: /** @todo enable Shift-rotate for battlescape too when issues are solved */ if (!Key_IsDown(K_SHIFT) || CL_BattlescapeRunning()) break; case INV_FITS_ONLY_ROTATED: previewItem.rotated = true; } /* Hack, no preview for armour, we don't want it out of the armour container (and armour container is not visible) */ if (INV_IsArmour(previewItem.item)) return; UI_GetNodeAbsPos(target, origine); origine[2] = -40; /* Get center of single container for placement of preview item */ if (EXTRADATA(target).container->single) { origine[0] += target->box.size[0] / 2.0; origine[1] += target->box.size[1] / 2.0; /* This is a "grid" container - we need to calculate the item-position * (on the screen) from stored placement in the container and the * calculated rotation info. */ } else { if (previewItem.rotated) { origine[0] += (dragInfoToX + previewItem.item->sy / 2.0) * C_UNIT; origine[1] += (dragInfoToY + previewItem.item->sx / 2.0) * C_UNIT; } else { origine[0] += (dragInfoToX + previewItem.item->sx / 2.0) * C_UNIT; origine[1] += (dragInfoToY + previewItem.item->sy / 2.0) * C_UNIT; } } UI_DrawItem(NULL, origine, &previewItem, -1, -1, scale, colorPreview); }
/** * @brief Tries to add an item to a container (in the inventory inv). * @param[in] self The inventory interface pointer * @param[in] inv Inventory pointer to add the item. * @param[in] item Item to add to inventory. * @param[in] container Container id. * @sa INVSH_FindSpace * @sa I_AddToInventory */ static bool I_TryAddToInventory (inventoryInterface_t* self, inventory_t* const inv, const item_t * const item, const invDef_t * container) { int x, y; INVSH_FindSpace(inv, item, container, &x, &y, NULL); if (x == NONE) { assert(y == NONE); return false; } else { const int checkedTo = INVSH_CheckToInventory(inv, item->item, container, x, y, NULL); if (!checkedTo) return false; const bool rotated = checkedTo == INV_FITS_ONLY_ROTATED; item_t itemRotation = *item; itemRotation.rotated = rotated; return self->AddToInventory(self, inv, &itemRotation, container, x, y, 1) != NULL; } }
/** * @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; }