/** ClonkInventoryControl Handles the Clonk's interaction with the inventory. */ /* used properties: this.inventory.is_picking_up: whether currently picking up this.inventory.quick_slot: slot that is currently selected for quick switching this.inventory.hotkey_down: the number of a hotkey being held down this.inventory.quick_slot_switched: true if the quick slot was switched during pressing down of a hotkey, the hotkey release will do nothing this.inventory.slots_switched: true if two inventory were switched (pressed a hotkey while another one was pressed); the hotkey release will do nothing other used properties of "this.inventory" might have been declared in Inventory.ocd */ func Construction() { if(this.inventory == nil) this.inventory = {}; this.inventory.quick_slot = 1; this.inventory.hotkey_down = nil; return _inherited(...); } public func OnShiftCursor(object new_cursor) { if (this.control.is_interacting) AbortPickingUp(); return _inherited(new_cursor, ...); } public func GetQuickSwitchSlot() { return this.inventory.quick_slot; } // Called by other libraries and objects when the Clonk has forcefully dropped (not thrown) an object. func OnDropped(object obj) { return _inherited(obj, ...); } func RejectCollect(id objid, object obj) { var rejected = _inherited(objid, obj, ...); if(rejected) return rejected; // Allow collection only if called via clonk->Collect, to prevent picking up stuff on the ground. // Make an exception for containers, though. if (!this.inventory.force_collection && !obj->Contained()) return true; return false; } public func ObjectControl(int plr, int ctrl, int x, int y, int strength, bool repeat, int status) { if (!this) return inherited(plr, ctrl, x, y, strength, repeat, status, ...); // Quickswitch changes the current active inventory slot if (ctrl == CON_QuickSwitch && status == CONS_Down) { // but ignore quickswitch if we have more than 1 hand-slot if(this.HandObjects > 1) return inherited(plr, ctrl, x, y, strength, repeat, status, ...);; // A number key (hotkey) is pressed, change quick switch slot /*if (this.inventory.hotkey_down != nil) { if (SetQuickSwitchSlot(this.inventory.hotkey_down-1)) this.inventory.quick_slot_switched = true; return true; }*/ // Otherwise select slot SetHandItemPos(0, this.inventory.quick_slot); // quick_slot is updated in SetHandItemPos return true; } if (ctrl == CON_QuickSwitch && status == CONS_Up) // Do nothing for now but will be used in the future { return true; } // Collection and dropping is only allowed when the Clonk is not contained. if (!Contained()) { // Quick-pickup item via click? Note that this relies on being executed after the normal Clonk controls if (ctrl == CON_Use && !this->GetHandItem(0) && status == CONS_Down) { var sort = Sort_Distance(x, y); var items = FindAllPickupItems(sort); for (var item in items) { if (item && TryToCollect(item)) return true; } } // Begin picking up objects. if (ctrl == CON_PickUp && status == CONS_Down) { this->CancelUse(); BeginPickingUp(); return true; } // Drop the mouse item? if (ctrl == CON_Drop && status == CONS_Down) { // Do not immediately collect another thing unless chosen with left/right. if (this.inventory.is_picking_up) { SetNextPickupItem(nil); } var item = this->GetHandItem(0); if (item) this->DropInventoryItem(this->GetHandItemPos(0)); return true; } // Switching pickup object or finish pickup? if (this.inventory.is_picking_up) { // Stop picking up. if (ctrl == CON_PickUpNext_Stop) { AbortPickingUp(); return true; } // Quickly pick up everything possible. if (ctrl == CON_PickUpNext_All) { PickUpAll(); AbortPickingUp(); return true; } // Finish picking up (aka "collect"). if (ctrl == CON_PickUp && status == CONS_Up) { EndPickingUp(); return true; } // Switch left/right through objects. var dir = nil; if (ctrl == CON_PickUpNext_Left) dir = -1; else if (ctrl == CON_PickUpNext_Right) dir = 1; if (dir != nil) { var item = FindNextPickupObject(this.inventory.pickup_item, dir); if (item) SetNextPickupItem(item); return true; } } } else // Contained { // If we are contained, have a picking up process running, and issue another command we first stop the selection. if (this.inventory.is_picking_up) AbortPickingUp(); } // shift inventory var inventory_shift = 0; if (ctrl == CON_InventoryShiftForward) inventory_shift = 1; else if (ctrl == CON_InventoryShiftBackward) inventory_shift = -1; if (inventory_shift) { var current = (this->GetHandItemPos(0) + inventory_shift) % this.MaxContentsCount; if (current < 0) current = this.MaxContentsCount + current; this->SetHandItemPos(0, current); return true; } var hot; // dropping items via hotkey hot = 0; if (ctrl == CON_DropHotkey0) hot = 10; if (ctrl == CON_DropHotkey1) hot = 1; if (ctrl == CON_DropHotkey2) hot = 2; if (ctrl == CON_DropHotkey3) hot = 3; if (ctrl == CON_DropHotkey4) hot = 4; if (ctrl == CON_DropHotkey5) hot = 5; if (ctrl == CON_DropHotkey6) hot = 6; if (ctrl == CON_DropHotkey7) hot = 7; if (ctrl == CON_DropHotkey8) hot = 8; if (ctrl == CON_DropHotkey9) hot = 9; if (hot > 0) { // Do not stop picking-up but nullify object selection. // That way, you can still drop during pickup, but you won't automatically pickup stuff when you try to drop. if (this.inventory.is_picking_up) { SetNextPickupItem(nil); } this->~DropInventoryItem(hot-1); return true; } // inventory hot = 0; if (ctrl == CON_Hotkey0) hot = 10; if (ctrl == CON_Hotkey1) hot = 1; if (ctrl == CON_Hotkey2) hot = 2; if (ctrl == CON_Hotkey3) hot = 3; if (ctrl == CON_Hotkey4) hot = 4; if (ctrl == CON_Hotkey5) hot = 5; if (ctrl == CON_Hotkey6) hot = 6; if (ctrl == CON_Hotkey7) hot = 7; if (ctrl == CON_Hotkey8) hot = 8; if (ctrl == CON_Hotkey9) hot = 9; // another hotkey is already pressed if (this.inventory.hotkey_down != nil && hot > 0 && hot <= this.MaxContentsCount && this.inventory.hotkey_down != hot) { // do nothing if this is just key down if (status == CONS_Down) return true; // switch the two slots this->~Switch2Items(this.inventory.hotkey_down-1, hot-1); //this.inventory.slots_switched = true; // This needs some explanation: // In the event of the Clonk window ever losing focus, a hotkey might still be registered as being held down. // If this was ever the case, the inventory would constantly switch around unless the exact same hotkey is pressed // again to trigger a release. This could very well confuse players as it is not obvious which key needs to be // pressed. With this, there will only be one switch and afterwards the inventory works just fine. // The downside is that after one switch a hotkey has be pressed again for another switch. this.inventory.hotkey_down = nil; return true; } // hotkey up: perform slot selection if (hot > 0 && hot <= this.MaxContentsCount && status == CONS_Up) { // This wasn't liked by many players, so slot selection is back to key down. // Only perform slot selection if nothing happened in the meantime /*if (!this.inventory.quick_slot_switched) if (!this.inventory.slots_switched) SetHandItemPos(0, hot-1);*/ this.inventory.hotkey_down = nil; //this.inventory.quick_slot_switched = false; //this.inventory.slots_switched = false; return true; } // a hotkey is pressed, save it for now if (hot > 0 && hot <= this.MaxContentsCount && status == CONS_Down) { this.inventory.hotkey_down = hot; // For safety //this.inventory.quick_slot_switched = false; //this.inventory.slots_switched = false; SetHandItemPos(0, hot-1); return true; } return inherited(plr, ctrl, x, y, strength, repeat, status, ...); } private func FxIntHighlightItemStart(object target, proplist fx, temp, object item) { if (temp) return; fx.item = item; fx.dummy = CreateObject(Dummy, item->GetX() - GetX(), item->GetY() - GetY(), GetOwner()); fx.dummy.ActMap = { Attach = { Name = "Attach", Procedure = DFA_ATTACH, FacetBase = 1 } }; fx.dummy.Visibility = VIS_Owner; fx.dummy.Plane = 1000; fx.dummy->Message("@%s", item->GetName()); // Center dummy! fx.dummy->SetVertexXY(0, item->GetVertex(0, VTX_X), item->GetVertex(0, VTX_Y)); fx.dummy->SetAction("Attach", item); fx.width = item->GetDefWidth(); fx.height = item->GetDefHeight(); // Draw the item's graphics in front of it again to achieve a highlighting effect. fx.dummy->SetGraphics(nil, nil, 1, GFXOV_MODE_Object, nil, GFX_BLIT_Additive, item); // Custom selector particle if object says so if (item->~PickupHighlight(fx.dummy, fx.width, fx.height, GetOwner())) return; // Draw a nice selector particle on item change. var selector = { Size = PV_Step(3, 2, 1, Max(fx.width, fx.height)), Attach = ATTACH_Front, Rotation = PV_Step(1, PV_Random(0, 360), 1), Alpha = 200 }; fx.dummy->CreateParticle("Selector", 0, 0, 0, 0, 0, Particles_Colored(selector, GetPlayerColor(GetOwner())), 1); } private func FxIntHighlightItemTimer(object target, proplist fx, int time) { if (!fx.dummy) return -1; if (!fx.item) return -1; if (ObjectDistance(this, fx.item) > 20) return -1; if (fx.item->Contained()) return -1; } private func FxIntHighlightItemStop(object target, proplist fx, int reason, temp) { if (temp) return; if (fx.dummy) fx.dummy->RemoveObject(); if (!this) return; if (fx.item == this.inventory.pickup_item) this.inventory.pickup_item = nil; } private func SetNextPickupItem(object to) { // Clear all old markers. var e = nil; while (e = GetEffect("IntHighlightItem", this)) RemoveEffect(nil, this, e); // And set & mark new one. this.inventory.pickup_item = to; if (to) AddEffect("IntHighlightItem", this, 1, 2, this, nil, to); } private func FindAllPickupItems(sorting_criterion) { return FindObjects(Find_Distance(20), Find_NoContainer(), Find_Property("Collectible"), Find_Layer(this->GetObjectLayer()), Find_Not(Find_OCF(OCF_HitSpeed1)), sorting_criterion); } private func FindNextPickupObject(object start_from, int x_dir) { if (!start_from) start_from = this; // Returns objects sorted by ascending x-position, with all objects right of the clonk being sorted before objects left of the clonk var sort = Sort_Func("Library_ClonkInventoryControl_Sort_Priority", start_from->GetX()); var objects = FindAllPickupItems(sort); var len = GetLength(objects); if (!len) return nil; // Find object next to the current one. // (note that index==-1 accesses the last element) var index = GetIndexOf(objects, start_from); if (index != -1) { // Previous item was found in the list. // Cycle through list to the right (x_dir==1) or left (x_dir==-1) index = (index + x_dir) % len; } else { // Previous item is no longer in range or there was no previous item - start with item closest to clonk position // Going only in view direction may sound intuitive, but is weird in practice when you're standing just on top // of an object that is 1px behind you and it selects an item very far away instead // The most intuitive seems to pre-select the item closest to the clonk hands (also e.g. when scaling), // so use distance from object center. index += (ObjectDistance(objects[-1]) > ObjectDistance(objects[0])); } var next = objects[index]; if (next == start_from) return nil; return next; } private func BeginPickingUp() { this.inventory.is_picking_up = true; var obj = FindNextPickupObject(this, 0); if (obj) SetNextPickupItem(obj); } // Ends the pickup process without actually doing anything. private func AbortPickingUp() { this.inventory.pickup_item = nil; EndPickingUp(); } private func EndPickingUp() { this.inventory.is_picking_up = false; if (this.inventory.pickup_item) { TryToCollect(this.inventory.pickup_item); } var e = nil; while (e = GetEffect("IntHighlightItem", this)) { RemoveEffect(nil, this, e); } this.inventory.pickup_item = nil; } // Shows an effect when you picked up an item. private func TryToCollect(object item) { // Remember stuff for a possible message - the item might have removed itself later. var x = item->GetX(); var y = item->GetY(); var name = item->GetName(); // When pushing a lorry, try to directly collect it into the lorry first. var vehicle = GetActionTarget(); if (vehicle && vehicle->~IsContainer() && GetProcedure() == "PUSH") { vehicle->Collect(item); } // Otherwise, try to collect the item myself. if (item && !item->Contained()) Collect(item); // If anything happened, assume collection. if (!item || item->Contained()) { var message = CreateObject(FloatingMessage, AbsX(x), AbsY(y), GetOwner()); message.Visibility = VIS_Owner; message->SetMessage(name); message->SetYDir(-10); message->FadeOut(1, 20); return true; } return false; } // Pick up all objects in the vicinity. private func PickUpAll() { // First try to find objects with the ID of the currently selected object. var preferred_id = nil; if (this.inventory.pickup_item != nil) preferred_id = this.inventory.pickup_item->GetID(); var sort = Sort_Distance(); var all_objects = FindAllPickupItems(sort); // Try the preferred id first. if (preferred_id) for (var obj in all_objects) { if (!obj || obj->Contained()) continue; if (obj->GetID() != preferred_id) continue; TryToCollect(obj); } // And try the others now.. for (var obj in all_objects) { if (!obj || obj->Contained()) continue; TryToCollect(obj); } } // Used in Inventory.ocd public func SetHandItemPos(int hand, int inv) { // Save the current slot as the last slot only for the first hand // and if the inventory slot actually changes. if (hand == 0 && this->GetHandItemPos(0) != inv) this.inventory.quick_slot = this->GetHandItemPos(0); return _inherited(hand, inv, ...); } public func SetQuickSwitchSlot(int slot) { // Do not set if the quick switch slot doesn't change if (slot == this.inventory.quick_slot) return false; // Do not set if slot is currently selected if (slot == this->GetHandItemPos(0)) return false; this.inventory.quick_slot = slot; // Notify HUD this->~OnInventoryChange(); this->~UpdateAttach(); return true; } /* Backpack control */ func Selected(object mnu, object mnu_item) { var backpack_index = mnu_item->GetExtraData(); var hands_index = 0; // Update menu var show_new_item = this->GetItem(hands_index); mnu_item->SetSymbol(show_new_item); // swap index with backpack index this->Switch2Items(hands_index, backpack_index); }