-
-
Save Silvyre/7e73756f108dc987a0d12d609e615371 to your computer and use it in GitHub Desktop.
javascript:(() => { | |
const versionInfo = 'π₯β¨οΈ colonist.io hotkeys :: v0.1 (by Ungar)'; | |
/* Initial set-up instructions: | |
0. (I suggest performing these steps after the initial game setup is completed.) | |
1. Open a DevTools window (F12). In the 'Sources' (Chrome) or 'Debugger' (Firefox) tab, open and 'pretty-print' dist/lobby-<hash>.min.js. | |
2. Place a breakpoint near the end of the UIGameManager.buildGame function's definition (around line 21965 in Chrome, or line 38330 in Firefox). | |
3. Refresh your colonist.io browser window. When the breakpoint you placed is hit, your window will pause its loading and your DevTools tab will enter debugging mode. Some lag is to be expected at this point. | |
4. Within the 'Console' tab of your DevTools window, copy and paste the contents of this file in its entirity. | |
5. Press the Enter key and wait for the version info to be logged to the console (this can take up to 30 seconds in large games). Again, some lag is to be expected while in debugging mode. | |
6. Unpause the debugger (there should be 'play' buttons on both your colonist.io window and DevTools windows to do so). Your colonist.io window should now resume loading as normal, and you can use the keybindings defined below (on lines 90+). | |
Notes: | |
- Your browser should save the breakpoint you placed in the pretty-printed lobby script. As such, step #2 only needs to be performed once. However, performing step 1 is still necessary (to load and enable that breakpoint). | |
- If you get a yellow 'disconnected' banner, just refresh your page and try again (or 'Reconnect' from the lobby). | |
- If the keybindings aren't working, try clicking on the game canvas. (This needs to be done after chatting, too.) | |
- Although pressing the spacebar does request a special build phase, doing so does not visually affect the special build flag. | |
- This script is a work-in-progress, and I'm open to feedback/suggestions (Discord: Silvyre#0561) | |
To-do: | |
- Allow number keys to select resources in non-trade circumstances, e.g. when discarding | |
- Allow number(?) keys to select choice of progress card | |
- Allow a key to cycle through progress card context menus | |
- Allow this key to cycle through (non-VP) development cards | |
- Allow 'k' key to play a Knight development card | |
*/ | |
if (this instanceof Window || !r || !T) { | |
console.error('colonist.io hotkeys error: please follow instructions regarding DevTools usage.'); | |
return; | |
} | |
const uiGameManager = this; | |
const uiGameTradeInfoController = new r.UIGameTradeInfoController(this); | |
const e_ = T.GameStateEnums.CardEnum; | |
const { socketGameSend, tradeController, gameState } = uiGameManager; | |
const { mapState, myColor } = gameState; | |
const { tileCorners, tileEdges } = mapState.tileState; | |
const getMyCorners = () => { | |
return tileCorners.reduce((acc, tc, idx) => { | |
if (tc.owner == myColor) { | |
acc.push(idx); | |
} | |
return acc; | |
}, []); | |
}; | |
const getMyEdges = () => { | |
return tileEdges.reduce((acc, te, idx) => { | |
if (te.owner == myColor) { | |
acc.push(idx); | |
} | |
return acc; | |
}, []); | |
}; | |
const addToOffer = resource => { | |
try { tradeController.clickedTrade(); } | |
catch {} | |
setTimeout(() => { | |
tradeController.leftExchangeController.clickedResourceCard(resource); | |
}, 100); | |
} | |
const addToWanted = resource => { | |
try { tradeController.clickedTrade(); } | |
catch {} | |
setTimeout(() => { | |
tradeController.rightExchangeController.exchangeView.cardContainer.addNewCardOfType(resource); | |
}, 100); | |
} | |
const offerWasAcceptedByMe = offer => { | |
return uiGameTradeInfoController.tradeInfoContainer?.children?.some(c => { | |
const playerIcon = c?.leftProposal?.children[2]; | |
const acceptedOfferFound = playerIcon?.children[1]?._texture?.textureCacheIds?.includes('icon_check'); | |
return acceptedOfferFound && c?.offer && offer?.id == c?.offer?.id && playerIcon?.playerState?.playerColorId == myColor; | |
}); | |
} | |
const remainingOffersAreUncounterable = idx => { | |
const remainingOffers = uiGameTradeInfoController.offers.slice(idx + 1); | |
return !remainingOffers.some(offer => { | |
return !offer.isCounterOffer; | |
}); | |
} | |
document.onkeypress = () => { | |
if (event.target.nodeName == 'INPUT') { | |
return; | |
} | |
switch(event.key) { | |
case 'r': /* build road at spot of choice */ | |
socketGameSend.buildRoad(); | |
break; | |
case 's': /* build settlement at spot of choice */ | |
socketGameSend.buildSettlement(); | |
break; | |
case 'c': /* build city upon settlement of choice */ | |
socketGameSend.buildCity(); | |
break; | |
case 'C': /* build city upon randomly-chosen settlement */ | |
socketGameSend.buildCity(); | |
getMyCorners().forEach(i => socketGameSend.confirmBuildCity(i)); | |
break; | |
case 'i': /* buy development card or improve progress tracks */ | |
try { socketGameSend.buyDevCard(); } | |
catch {} | |
[0, 1, 2].forEach(i => socketGameSend.confirmCityUpgrade(i)); | |
break; | |
case 'w': /* build city wall upon city of choice */ | |
socketGameSend.buildCityWall(); | |
break; | |
case 'W': /* build city wall upon randomly-chosen city */ | |
socketGameSend.buildCityWall(); | |
getMyCorners().forEach(i => socketGameSend.confirmBuildCityWall(i)); | |
break; | |
case 'o': /* build ship at spot of choice */ | |
socketGameSend.buildShip(); | |
break; | |
case 'p': /* reposition ship of choice */ | |
socketGameSend.moveShip(); | |
break; | |
case 'P': /* reposition randomly-chosen ship */ | |
socketGameSend.moveShip(); | |
getMyEdges().forEach(i => socketGameSend.selectedShipToMove(i)); | |
break; | |
case 'k': /* build knight at spot of choice */ | |
socketGameSend.placeKnight(); | |
break; | |
case 'f': /* 'feed' (activate) your knight of choice */ | |
socketGameSend.activateKnight(); | |
break; | |
case 'F': /* 'feed' (activate) randomly-chosen knight */ | |
socketGameSend.activateKnight(); | |
getMyCorners().forEach(i => socketGameSend.confirmActivateKnight(i)); | |
break; | |
case 'u': /* upgrade your choice of knight */ | |
socketGameSend.upgradeKnight(); | |
break; | |
case 'U': /* upgrade randomly-chosen knight */ | |
socketGameSend.upgradeKnight(); | |
getMyCorners().forEach(i => socketGameSend.confirmUpgradeKnight(i)); | |
break; | |
case 'a': /* act with your choice of knight */ | |
socketGameSend.takeKnightAction(); | |
break; | |
case 'A': /* act with randomly-chosen knight */ | |
socketGameSend.takeKnightAction(); | |
getMyCorners().forEach(i => socketGameSend.confirmSelectKnightToTakeAction(i)); | |
break; | |
case ' ': /* (spacebar) request special build phase */ | |
socketGameSend.requestSpecialBuildPhase(); | |
break; | |
case '1': /* add lumber to left side of trade window */ | |
addToOffer(e_.Lumber); | |
break; | |
case '!': /* add lumber to right side of trade window */ | |
addToWanted(e_.Lumber); | |
break; | |
case '2': /* add brick to left side of trade window */ | |
addToOffer(e_.Brick); | |
break; | |
case '@': /* add brick to right side of trade window */ | |
addToWanted(e_.Brick); | |
break; | |
case '3': /* add wool to left side of trade window */ | |
addToOffer(e_.Wool); | |
break; | |
case '#': /* add wool to right side of trade window */ | |
addToWanted(e_.Wool); | |
break; | |
case '4': /* add grain to left side of trade window */ | |
addToOffer(e_.Grain); | |
break; | |
case '$': /* add grain to right side of trade window */ | |
addToWanted(e_.Grain); | |
break; | |
case '5': /* add ore to left side of trade window */ | |
addToOffer(e_.Ore); | |
break; | |
case '%': /* add ore to right side of trade window */ | |
addToWanted(e_.Ore); | |
break; | |
case '6': /* add cloth to left side of trade window */ | |
addToOffer(e_.Cloth); | |
break; | |
case '^': /* add cloth to right side of trade window */ | |
addToWanted(e_.Cloth); | |
break; | |
case '7': /* add coin to left side of trade window */ | |
addToOffer(e_.Coin); | |
break; | |
case '&': /* add coin to right side of trade window */ | |
addToWanted(e_.Coin); | |
break; | |
case '8': /* add paper to left side of trade window */ | |
addToOffer(e_.Paper); | |
break; | |
case '*': /* add paper to right side of trade window */ | |
addToWanted(e_.Paper); | |
break; | |
case ':': /* submit your trade offer */ | |
tradeController.clickedActionButton(); | |
break; | |
case '=': /* take first (top-leftmost) accepted outgoing trade offer */ | |
let offerToTake, tradingWith; | |
uiGameTradeInfoController.tradeInfoContainer?.children?.some(c => { | |
const playerIcon = c?.creatorProposal?.children[4]?.children[1]; | |
const acceptedOfferFound = playerIcon?.children[1]?._texture?.textureCacheIds?.includes('icon_check'); | |
if (acceptedOfferFound) { | |
offerToTake = c.offer; | |
tradingWith = playerIcon.playerState.playerColorId; | |
} | |
return acceptedOfferFound; | |
}); | |
if (offerToTake) { | |
socketGameSend.takeAcceptedOffer(offerToTake.id, tradingWith); | |
} | |
break; | |
case '[': /* accept first unaccepted incoming trade offer */ | |
uiGameTradeInfoController.offers.some(({ offer }) => { | |
if (!offer.isCounterOffer && !offerWasAcceptedByMe(offer)) { | |
socketGameSend.acceptedOffer(offer.id); | |
offer.updateResponse(myColor, 1); | |
return true; | |
} | |
}); | |
break; | |
case ']': /* reject first unaccepted incoming trade offer */ | |
uiGameTradeInfoController.offers.some(({ offer }) => { | |
if (!offer.isCounterOffer && !offerWasAcceptedByMe(offer)) { | |
socketGameSend.rejectedOffer(offer.id); | |
return true; | |
} | |
}); | |
break; | |
case '{': /* start counter-offer on last/non-accepted incoming trade offer */ | |
uiGameTradeInfoController.offers.some(({ offer, idx }) => { | |
if ( | |
!offer.isCounterOffer && (!offerWasAcceptedByMe(offer) || remainingOffersAreUncounterable(idx)) | |
) { | |
tradeController.editOffer(offer); | |
return true; | |
} | |
}); | |
break; | |
case '/': /* reset trade window */ | |
tradeController.resetTradeWindow(); | |
break; | |
case ',': /* reset left side of trade window */ | |
tradeController.leftExchangeController.updatePlayerCards(); | |
break; | |
case '.': /* reset right side of trade window */ | |
tradeController.rightExchangeController.updateAndShowUI(); | |
break; | |
case '`': /* cancels current action, closes trade window, dismisses pop-ups */ | |
try { socketGameSend.cancelAction(); } | |
catch {} | |
try { tradeController.closeTradeUI(); } | |
catch {} | |
try { document.querySelector('.popup .btn_general_check')?.click(); } | |
catch {} | |
break; | |
case 'd': /* roll the dice */ | |
socketGameSend.clickedDice(); | |
break; | |
case '\\': /* end your turn and/or reject all trade offers (the backslash key only needs to be single-clicked) */ | |
try { | |
uiGameTradeInfoController.offers.forEach(({ offer }) => { | |
socketGameSend.rejectedOffer(offer.id); | |
}); | |
socketGameSend.cancelAction(); | |
tradeController.closeTradeUI(); | |
} catch {} | |
socketGameSend.clickedPassTurn(); | |
break; | |
} | |
}; | |
console.log(versionInfo); | |
})(); |
Hi there.
A few suggestions about your settings.
The setup action with the Tab load begins
event should use the Event URL
condition instead of the URL
condition.
The URL
condition applies to the focused tab, whereas the Event URL
condition applies to the tab that triggered the Tab load begins
event. The latter is what you want to achieve here.
Also, on the action side, you must apply the script to the Event tab
. This way the script will be injected into the colonist.io tab whether it's the focused tab or not.
It's also preferable to add a wildcard to the Tab load begins
event (as shown above). This way the trigger will fire the action even if some keys or buttons are being held down at the exact moment the Tab load begins
event occurred.
It might also work better in your case to use the Tab load ends
event instead of Tab load begins
. That way your script will be injected when the page is fully loaded and all functions and variables in the page (that your script might depend on) are already initialized.
Thanks for the tips, @AutoControl-app! β€οΈ
This is awesome, I love it a ton.
One weird thing I've noticed is the selecting, e.g. when building a road. The direction you press doesn't correspond with what you see, it's following a different path. Haven't looked at it myself, but possibly something they did changing the layout/code since it was written?
Hey @CrookedJ, glad to hear you're enjoying ACCio!
The hex edge selection logic I implemented isn't very smart: it just cycles through the (unordered) list of highlighted edges without first attempting to find the edge that best corresponds to the arrow key pressed:
getHighlightInDirection = dir => {
if (multiplePartTypesHighlighted()) {
return getNextHighlightInArray(dir);
}
const hlPart = getHighlightedPart();
if (hlPart == 'Corner') {
... // sometimes smart, sometimes not smart
}
else if (hlPart == 'Edge') {
return getNextHighlightInArray(dir); // not smart!
}
...
}
A better edge selection algorithm would search through the list of highlighted edges and find the closest edge in the direction pressed.
After this week's update, using the .
hotkey to end turn also prompts for a username change.
EDIT:
So I looked some more and did some testing. End turn actually does 3 things,
- (script43 - cancel.js) cancel actions/close trade ui/close popups
- (script59 - tradeRejectAll.js) reject trade offers
- (script45 - endTurn.js) end turn
cancel.js is the issue (same one that runs when hitting ESC
) - pressing ESC
also pops the username change popup
Hope that helps, thanks
EDIT 2:
Shift+Enter also makes it popup
but it doesn't ALWAYS popup. π€·ββοΈ
@CrookedJ I made a change to fix this in revision #111. If you find any pop-ups that you are no longer able to close using ACCio as a result of this change, please let me know! Thank you!
Will do, cheers mate!
Found a bug - End of game scoreboard doesn't show up
EDIT: Well it did on another game so not sure
AutoControl for Colonist.io (ACCio) π§π
Features
π§ Fully-customizable hotkeys for building, trading and more!
ACCio.interface.mp4
ACCio is powered by the AutoControl extension for Chrome, which allows for extremely versatile hotkey customization.
π "Open-ended" trading!
Open.ended.trading.mp4
Easily create "open-ended" trade offers!
β‘ Build lightning fast!
Build.things.lightning.fast.with.ACCio.mp4
Click-free construction! Use your arrow keys to move game pieces!
π± Trading = Typing!
(Video preview coming soon!)
One-click counter-offers? Yes, please!
π Other features
Setup (Chrome on Windows)
(Note on system requirements: ACCio is only compatible with Chrome on Windows. If you use another operating systemβ βor would prefer to not use Chromeβ βyou may wish to try out my colonist_hotkeys.js script.)
chrome-extension://lkaihdpfpifdlgoapbfocpmekbokmcfd/main.html#options
https://gist.githubusercontent.com/Silvyre/7e73756f108dc987a0d12d609e615371/raw/ACCio.dat
At this point, ACCio's hotkeys should be usable after loading into any game.
AutoControl's documentation can be found here.
Notes
Silvyre#0561
)To-do
Known Issues