Skip to content

Instantly share code, notes, and snippets.

@Silvyre
Last active May 20, 2024 18:16
Show Gist options
  • Save Silvyre/7e73756f108dc987a0d12d609e615371 to your computer and use it in GitHub Desktop.
Save Silvyre/7e73756f108dc987a0d12d609e615371 to your computer and use it in GitHub Desktop.
πŸ§™πŸ‘ AutoControl for Colonist.io (ACCio): play Colonist.io using your keyboard! Setup instructions in comments below.
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);
})();
@Silvyre
Copy link
Author

Silvyre commented Dec 7, 2021

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

  • Control all building and Development/Progress Cards using your keyboard.
  • Persistently toggle C&K player information views (e.g. to view everyone's Progress Card VP and Defender VP) without hovering.
  • Color-coded trade ratios and bank amounts.
  • Helpful reminders and warnings conditionally displayed on various pop-ups.
  • And much more!

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.)

  1. Add the AutoControl extension for Chrome.
  2. Open AutoControl's user interface, which can be accessed by clicking on the AutoControl extension's button within Chrome's Extensions menu, or by pasting this URL into your browser's address bar:
    chrome-extension://lkaihdpfpifdlgoapbfocpmekbokmcfd/main.html#options
  3. Within the "Options" page, under the "Synchronize, Save, Restore Settings" heading, click on the "Restore from File" button.
  4. Within the File Explorer window that pops up, instead of selecting a file, copy, paste and enter the following URL:
    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

  • If any hotkeys don't work after entering a game, try using the 'manual setup' hotkey (Ctrl+Alt+Shift+A). If that doesn't work, refreshing should.
  • If the hotkeys suddenly stop working, try clicking on the game canvas. (This needs to be done after chatting, too.)
  • ACCio is a work-in-progress, and I'm open to feedback/suggestions/contributions (Discord: Silvyre#0561)

To-do

  • More promotional video clips (in-progress!)

Known Issues

  • Some hotkeys for accepting/confirming trades are not functioning perfectly. Likely an easy fix.
  • Commercial Harbour sometimes requires refreshing afterward to fix your hand. Also likely an easy fix.
  • C&K Aqueduct/Gold warning messages may not show up the first time after loading into a game. (This is due to the roundabout way the message text gets added.)

@AutoControl-app
Copy link

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.
image

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.

@Silvyre
Copy link
Author

Silvyre commented Jan 23, 2022

Thanks for the tips, @AutoControl-app! ❀️

@CrookedJ
Copy link

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?

@Silvyre
Copy link
Author

Silvyre commented May 24, 2022

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.

@CrookedJ
Copy link

CrookedJ commented Jun 29, 2022

After this week's update, using the . hotkey to end turn also prompts for a username change.
image

EDIT:
So I looked some more and did some testing. End turn actually does 3 things,

  1. (script43 - cancel.js) cancel actions/close trade ui/close popups
  2. (script59 - tradeRejectAll.js) reject trade offers
  3. (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. πŸ€·β€β™‚οΈ

@Silvyre
Copy link
Author

Silvyre commented Jul 3, 2022

@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!

@CrookedJ
Copy link

CrookedJ commented Jul 3, 2022

Will do, cheers mate!

@CrookedJ
Copy link

CrookedJ commented Jul 3, 2022

Found a bug - End of game scoreboard doesn't show up

EDIT: Well it did on another game so not sure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment