--*********************************************************** --** THE INDIE STONE ** --*********************************************************** require "ISUI/ISRadialMenu" ISFirearmRadialMenu = ISBaseObject:derive("ISFirearmRadialMenu") ----- local BaseCommand = ISBaseObject:derive("BaseCommand") function BaseCommand:new(frm) local o = ISBaseObject.new(self) o.frm = frm o.character = frm.character return o end function BaseCommand:getWeapon() return self.frm:getWeapon() end function BaseCommand:fillMenu(menu, weapon) error "forgot to derive fillMenu()" end ----- local CInsertMagazine = BaseCommand:derive("CInsertMagazine") function CInsertMagazine:new(frm) local o = BaseCommand.new(self, frm) return o end function CInsertMagazine:fillMenu(menu, weapon) if weapon:isContainsClip() then return end if not weapon:getMagazineType() then return end local magazine = weapon:getBestMagazine(self.character) if not magazine then return end local text = getText("IGUI_FirearmRadial_InsertMagazine") local xln = "IGUI_FirearmRadial_AmmoCount" local textCount = getText(xln, magazine:getCurrentAmmoCount(), magazine:getMaxAmmo()) text = text .. '\n' .. textCount menu:addSlice(text, getTexture("media/ui/FirearmRadial_InsertMagazine.png"), self.invoke, self) end function CInsertMagazine:invoke() local weapon = self:getWeapon() if not weapon then return end if weapon:isContainsClip() then return end local magazine = weapon:getBestMagazine(self.character) if not magazine then return end ISInventoryPaneContextMenu.transferIfNeeded(self.character, magazine) ISTimedActionQueue.add(ISInsertMagazine:new(self.character, weapon, magazine)) end ----- local CEjectMagazine = BaseCommand:derive("CEjectMagazine") function CEjectMagazine:new(frm) local o = BaseCommand.new(self, frm) return o end function CEjectMagazine:fillMenu(menu, weapon) if not weapon:isContainsClip() then return end local text = getText("IGUI_FirearmRadial_EjectMagazine") local xln = weapon:isRoundChambered() and "IGUI_FirearmRadial_AmmoCountChambered" or "IGUI_FirearmRadial_AmmoCount" local textCount = getText(xln, weapon:getCurrentAmmoCount(), weapon:getMaxAmmo()) text = text .. '\n' .. textCount text = text:gsub("\\n", "\n") menu:addSlice(text, getTexture("media/ui/FirearmRadial_EjectMagazine.png"), self.invoke, self) end function CEjectMagazine:invoke() local weapon = self:getWeapon() if not weapon then return end if not weapon:isContainsClip() then return end ISTimedActionQueue.add(ISEjectMagazine:new(self.character, weapon)) end ----- local CLoadBulletsInMagazine = BaseCommand:derive("CLoadBulletsInMagazine") function CLoadBulletsInMagazine:new(frm) local o = BaseCommand.new(self, frm) return o end local function predicateNotFullMagazine(item, magazineType) return (item:getType() == magazineType or item:getFullType() == magazineType) and item:getCurrentAmmoCount() < item:getMaxAmmo() end local function predicateFullestMagazine(item1, item2) return item1:getCurrentAmmoCount() - item2:getCurrentAmmoCount() end function CLoadBulletsInMagazine:getMagazine(weapon) if not weapon:getMagazineType() then return nil end local inventory = self.character:getInventory() return inventory:getBestEvalArgRecurse(predicateNotFullMagazine, predicateFullestMagazine, weapon:getMagazineType()) end function CLoadBulletsInMagazine:hasBulletsForMagazine(magazine) local inventory = self.character:getInventory() return inventory:getCountTypeRecurse(magazine:getAmmoType()) > 0 end function CLoadBulletsInMagazine:fillMenu(menu, weapon) local magazine = self:getMagazine(weapon) if not magazine then return end if not self:hasBulletsForMagazine(magazine) then return end local text = getText("IGUI_FirearmRadial_LoadBulletsIntoMagazine") local xln = "IGUI_FirearmRadial_AmmoCount" local textCount = getText(xln, magazine:getCurrentAmmoCount(), magazine:getMaxAmmo()) text = text .. '\\n' .. textCount text = text:gsub('\\n', '\n') menu:addSlice(text, getTexture("media/ui/FirearmRadial_BulletsIntoMagazine.png"), self.invoke, self) end function CLoadBulletsInMagazine:invoke() local weapon = self:getWeapon() if not weapon then return end local magazine = self:getMagazine(weapon) if not magazine then return end if not self:hasBulletsForMagazine(magazine) then return end ISInventoryPaneContextMenu.transferIfNeeded(self.character, magazine) local ammoCount = ISInventoryPaneContextMenu.transferBullets(self.character, magazine:getAmmoType(), magazine:getCurrentAmmoCount(), magazine:getMaxAmmo()) if ammoCount == 0 then return end ISTimedActionQueue.add(ISLoadBulletsInMagazine:new(self.character, magazine, ammoCount)) end ----- local CLoadRounds = BaseCommand:derive("CLoadRounds") function CLoadRounds:new(frm) local o = BaseCommand.new(self, frm) return o end function CLoadRounds:hasBullets(weapon) local inventory = self.character:getInventory() return inventory:getCountTypeRecurse(weapon:getAmmoType()) > 0 end function CLoadRounds:fillMenu(menu, weapon) if weapon:getMagazineType() then return end if weapon:getCurrentAmmoCount() >= weapon:getMaxAmmo() then return end if not self:hasBullets(weapon) then return end local text = getText("IGUI_FirearmRadial_LoadRounds") local xln = weapon:isRoundChambered() and "IGUI_FirearmRadial_AmmoCountChambered" or "IGUI_FirearmRadial_AmmoCount" local textCount = getText(xln, weapon:getCurrentAmmoCount(), weapon:getMaxAmmo()) text = text .. '\\n' .. textCount text = text:gsub('\\n', '\n') menu:addSlice(text, getTexture("media/ui/FirearmRadial_BulletsIntoFirearm.png"), self.invoke, self) end function CLoadRounds:invoke() local weapon = self:getWeapon() if not weapon then return end if weapon:getMagazineType() then return end if weapon:getCurrentAmmoCount() >= weapon:getMaxAmmo() then return end if not self:hasBullets(weapon) then return end ISInventoryPaneContextMenu.transferBullets(self.character, weapon:getAmmoType(), weapon:getCurrentAmmoCount(), weapon:getMaxAmmo()) ISTimedActionQueue.add(ISReloadWeaponAction:new(self.character, weapon)) end ----- local CUnloadRounds = BaseCommand:derive("CUnloadRounds") function CUnloadRounds:new(frm) local o = BaseCommand.new(self, frm) return o end function CUnloadRounds:fillMenu(menu, weapon) if weapon:getMagazineType() then return end if weapon:getCurrentAmmoCount() == 0 then return end local text = getText("IGUI_FirearmRadial_UnloadRounds") local xln = weapon:isRoundChambered() and "IGUI_FirearmRadial_AmmoCountChambered" or "IGUI_FirearmRadial_AmmoCount" local textCount = getText(xln, weapon:getCurrentAmmoCount(), weapon:getMaxAmmo()) text = text .. '\\n' .. textCount text = text:gsub('\\n', '\n') menu:addSlice(text, getTexture("media/ui/FirearmRadial_BulletsFromFirearm.png"), self.invoke, self) end function CUnloadRounds:invoke() local weapon = self:getWeapon() if not weapon then return end if weapon:getMagazineType() then return end if weapon:getCurrentAmmoCount() == 0 then return end ISTimedActionQueue.add(ISUnloadBulletsFromFirearm:new(self.character, weapon, false)) end ----- local CRack = BaseCommand:derive("CRack") function CRack:new(frm) local o = BaseCommand.new(self, frm) return o end function CRack:fillMenu(menu, weapon) if not ISReloadWeaponAction.canRack(weapon) then return end local xln = weapon:isRoundChambered() and "IGUI_FirearmRadial_AmmoCountChambered" or "IGUI_FirearmRadial_AmmoCount" local textCount = getText(xln, weapon:getCurrentAmmoCount(), weapon:getMaxAmmo()) if weapon:isJammed() then local text = getText("IGUI_FirearmRadial_Unjam") text = text .. '\n' .. textCount menu:addSlice(text, getTexture("media/ui/FirearmRadial_Unjam.png"), self.invoke, self) elseif not weapon:haveChamber() or weapon:isRoundChambered() then local xln = weapon:haveChamber() and "IGUI_FirearmRadial_Rack" or "IGUI_FirearmRadial_UnloadRound" local text = getText(xln) text = text .. '\n' .. textCount menu:addSlice(text, getTexture("media/ui/FirearmRadial_Rack.png"), self.invoke, self) elseif weapon:haveChamber() then local text = getText("IGUI_FirearmRadial_ChamberRound") text = text .. '\n' .. textCount menu:addSlice(text, getTexture("media/ui/FirearmRadial_ChamberRound.png"), self.invoke, self) end end function CRack:invoke() local weapon = self:getWeapon() if not weapon then return end if not ISReloadWeaponAction.canRack(weapon) then return end ISTimedActionQueue.add(ISRackFirearm:new(self.character, weapon)) end ----- function ISFirearmRadialMenu:center() local menu = getPlayerRadialMenu(self.playerNum) local x = getPlayerScreenLeft(self.playerNum) local y = getPlayerScreenTop(self.playerNum) local w = getPlayerScreenWidth(self.playerNum) local h = getPlayerScreenHeight(self.playerNum) x = x + w / 2 y = y + h / 2 menu:setX(x - menu:getWidth() / 2) menu:setY(y - menu:getHeight() / 2) end function ISFirearmRadialMenu:getWeapon() local weapon = self.character:getPrimaryHandItem() if not weapon then return nil end if not instanceof(weapon, "HandWeapon") then return nil end if not weapon:isRanged() then return nil end return weapon end function ISFirearmRadialMenu:fillMenu() local menu = getPlayerRadialMenu(self.playerNum) menu:clear() local weapon = self:getWeapon() if not weapon then return end local commands = {} if weapon:getMagazineType() then if weapon:isContainsClip() then table.insert(commands, CEjectMagazine:new(self)) else table.insert(commands, CInsertMagazine:new(self)) end table.insert(commands, CLoadBulletsInMagazine:new(self)) else table.insert(commands, CLoadRounds:new(self)) table.insert(commands, CUnloadRounds:new(self)) end table.insert(commands, CRack:new(self)) for _,command in ipairs(commands) do local count = #menu.slices command:fillMenu(menu, weapon) if count == #menu.slices then menu:addSlice(nil, nil, nil) end end end function ISFirearmRadialMenu:display() local menu = getPlayerRadialMenu(self.playerNum) self:center() menu:addToUIManager() if JoypadState.players[self.playerNum+1] then menu:setHideWhenButtonReleased(Joypad.RBumper) setJoypadFocus(self.playerNum, menu) self.character:setJoypadIgnoreAimUntilCentered(true) end end function ISFirearmRadialMenu:new(character) local o = ISBaseObject.new(self) o.character = character o.playerNum = character:getPlayerNum() return o end ----- function ISFirearmRadialMenu.checkWeapon(playerObj) local weapon = playerObj:getPrimaryHandItem() if not weapon then return false end if not instanceof(weapon, "HandWeapon") then return false end if not weapon:isRanged() then return false end return true end ----- function ISFirearmRadialMenu.getBestLBButtonAction(buttonPrompt) -- buttonPrompt:setLBPrompt("RACK", nil, buttonPrompt.player) return false end local STATE = {} STATE[1] = {} STATE[2] = {} STATE[3] = {} STATE[4] = {} local function onJoypadReload(buttonPrompt) STATE[buttonPrompt.player+1].rbPressedMS = getTimestampMs() end function ISFirearmRadialMenu.getBestRBButtonAction(buttonPrompt) if JoypadState.disableReload then return false; end local playerObj = getSpecificPlayer(buttonPrompt.player) if playerObj:isAiming() or playerObj:isLookingWhileInVehicle() then return false end local queue = ISTimedActionQueue.queues[playerObj] if queue and #queue.queue > 0 then return false end if not ISFirearmRadialMenu.checkWeapon(playerObj) then return end buttonPrompt:setRBPrompt("RELOAD", onJoypadReload) return true end function ISFirearmRadialMenu.onRepeatRBumper(buttonPrompt) local radialMenu = getPlayerRadialMenu(buttonPrompt.player) local playerObj = getSpecificPlayer(buttonPrompt.player) if playerObj:isAiming() or playerObj:isLookingWhileInVehicle() then STATE[buttonPrompt.player+1].rbPressedMS = nil return false end if radialMenu:isReallyVisible() then return end if not STATE[buttonPrompt.player+1].rbPressedMS then return end local delay = 500 if getCore():getOptionReloadRadialInstant() then delay = 0 end if getTimestampMs() - STATE[buttonPrompt.player+1].rbPressedMS >= delay then local frm = ISFirearmRadialMenu:new(playerObj) frm:fillMenu() frm:display() setPlayerMovementActive(buttonPrompt.player, false) end end function ISFirearmRadialMenu.onJoypadButtonReleased(buttonPrompt, button) if button == Joypad.RBumper then local rbPressedMS = STATE[buttonPrompt.player+1].rbPressedMS if not rbPressedMS then return end STATE[buttonPrompt.player+1].rbPressedMS = nil local radialMenu = getPlayerRadialMenu(buttonPrompt.player) if radialMenu:isReallyVisible() then return end if getTimestampMs() - rbPressedMS < 500 then local playerObj = getSpecificPlayer(buttonPrompt.player) local weapon = playerObj:getPrimaryHandItem() ISReloadWeaponAction.BeginAutomaticReload(playerObj, weapon) end end end ----- function ISFirearmRadialMenu.checkKey(key) if key ~= getCore():getKey("ReloadWeapon") then return false end if UIManager.getSpeedControls() and (UIManager.getSpeedControls():getCurrentGameSpeed() == 0) then return false end local playerObj = getSpecificPlayer(0) if not playerObj or playerObj:isDead() then return false end local queue = ISTimedActionQueue.queues[playerObj] if queue and #queue.queue > 0 then return false end if getCell():getDrag(0) then return false end return true end function ISFirearmRadialMenu.onKeyPressed(key) if not ISFirearmRadialMenu.checkKey(key) then return end local radialMenu = getPlayerRadialMenu(0) if getCore():getOptionRadialMenuKeyToggle() and radialMenu:isReallyVisible() then STATE[1].radialWasVisible = true radialMenu:removeFromUIManager() return end if not ISFirearmRadialMenu.checkWeapon(getSpecificPlayer(0)) then return end STATE[1].keyPressedMS = getTimestampMs() STATE[1].radialWasVisible = false end function ISFirearmRadialMenu.onKeyRepeat(key) if not ISFirearmRadialMenu.checkKey(key) then return end if not ISFirearmRadialMenu.checkWeapon(getSpecificPlayer(0)) then return end if STATE[1].radialWasVisible then return end if not STATE[1].keyPressedMS then return end local radialMenu = getPlayerRadialMenu(0) local delay = 500 if getCore():getOptionReloadRadialInstant() then delay = 0 end if (getTimestampMs() - STATE[1].keyPressedMS >= delay) and not radialMenu:isReallyVisible() then local frm = ISFirearmRadialMenu:new(getSpecificPlayer(0)) frm:fillMenu() frm:display() end end function ISFirearmRadialMenu.onKeyReleased(key) if not ISFirearmRadialMenu.checkKey(key) then return end if not STATE[1].keyPressedMS then return end local radialMenu = getPlayerRadialMenu(0) if radialMenu:isReallyVisible() or STATE[1].radialWasVisible then if not getCore():getOptionRadialMenuKeyToggle() then radialMenu:removeFromUIManager() end return end if not ISFirearmRadialMenu.checkWeapon(getSpecificPlayer(0)) then return end if getCore():getOptionReloadRadialInstant() then return end if getTimestampMs() - STATE[1].keyPressedMS < 500 then local playerObj = getSpecificPlayer(0) local weapon = playerObj:getPrimaryHandItem() ISReloadWeaponAction.BeginAutomaticReload(playerObj, weapon) end end local function OnGameStart() Events.OnKeyStartPressed.Add(ISFirearmRadialMenu.onKeyPressed) Events.OnKeyKeepPressed.Add(ISFirearmRadialMenu.onKeyRepeat) Events.OnKeyPressed.Add(ISFirearmRadialMenu.onKeyReleased) end Events.OnGameStart.Add(OnGameStart)