---
description: 2021-08-31 发布于微博：https://weibo.com/ttarticle/p/show?id=2309404676185828491285
---

# Town Star 第 8 篇：自动售卖挂机脚本--没有编程基础也能轻松上手

Town Star 这款游戏**伤肝**，所以很多人玩到一般就放弃了。其实**国外供应商**是有相关NFT**售卖**，有一个NFT可以自动卖货，该NFT价值**0.11eth**。如下图：



![](https://3992198910-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMUnuyzmjRuPmBDiTwZ5i%2Fuploads%2FtxgdQSevhb8dTFPmYaf9%2F59cf2e705444a62a0f64a1dc868bc6d4_008idDKsly4gtzpmb3c66j30sg0lck1r.jpg?alt=media\&token=c4040732-ffb2-4987-aa1d-a302c4433768)

**Town Star交流群**600号人，我不想让200号人为了周赛的奖励，天天一起“肝”他。



所以狗哥将自己珍藏许久的脚本拿出来，祝大家在新的赛季中获得更高的奖励，同时也保护好大家的身体。脚本搭建如下：



### 一、**安装油猴**



在浏览器中搜索tampermonkey（**自行在后面加“.net”**），设置语言为“zh”，点击“下载”



![](https://3992198910-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMUnuyzmjRuPmBDiTwZ5i%2Fuploads%2FZAwRC07cPCo23W3Ggh2Q%2Fbf997d0490ceabf3ed7c7086b19f2018_008idDKsly4gtzpmb89qjj30u00cg422.jpg?alt=media\&token=045b0fe2-da30-4adc-a8f1-b462d6fda976)



### 二、**编辑脚本**



在谷歌插件中找到“tampermonkey”，点开，点击“**添加新脚本**”。

在编辑器中**输入以下代码**后**保存**（**一定要一字不漏**）：

>
>
> ```
> // ==UserScript==
> // @name         Town Star Auto-Sell
> // @namespace    http://tampermonkey.net/
> // @version      2.3
> // @description  Automatically sell crafted items.
> // @author       Groove
> // @match        https://townstar.sandbox-games.com/launch/
> // @grant        none
> // @run-at       document-start
> // ==/UserScript==
>
> (function() {
>     'use strict';
> 	// keepAmt is the amount that you do not want to sell
> 	// sellMin is the minimum amount needed before attempting to sell
> 	//    setting a sellMin of 100 will ensure that the item is only sold in batches of 100 (e.g. via Freight Ship)
>     const craftedItems = [
>         {item: 'Silica', keepAmt: 0, sellMin: 100},
>         {item: 'Pinot_Noir_Grapes', keepAmt: 0, sellMin: 0},
>         {item: 'Feed', keepAmt: 0, sellMin: 0},
>         {item: 'Wheat', keepAmt: 0, sellMin: 0},
>         {item: 'Flour', keepAmt: 0, sellMin: 0},
>         {item: 'Salt', keepAmt: 0, sellMin: 0},
>     ]
>
>     new MutationObserver(function(mutations) {
>         let airdropcollected = 0;
>         if(document.getElementsByClassName('hud-jimmy-button')[0] && document.getElementsByClassName('hud-jimmy-button')[0].style.display != 'none'){
>             document.getElementsByClassName('hud-jimmy-button')[0].click();
>             document.getElementById('Deliver-Request').getElementsByClassName('yes')[0].click();
>             document.getElementById('Deliver-Request').getElementsByClassName('close-button')[0].click();
>         }
>         if(document.getElementsByClassName('hud-airdrop-button')[0] && document.getElementsByClassName('hud-airdrop-button')[0].style.display != 'none'){
>             if(airdropcollected == 0){
>                 airdropcollected = 1;
>                 document.getElementsByClassName('hud-airdrop-button')[0].click();
>                 document.getElementsByClassName('air-drop')[0].getElementsByClassName('yes')[0].click();
>             }
>         }
>         if (document.getElementById("playnow-container") && document.getElementById("playnow-container").style.visibility !== "hidden") {
>             if(typeof Game == 'undefined' || (Game && Game.gameData == null)) {
>                 window.location.reload();
>             } else {
>                 document.getElementById("playButton").click();
>                 console.log(Date.now() + ' ---===ACTIVATING AUTO SELL===---');
>                 ActivateAutoSell();
>             }
>         }
>     }).observe(document, {childList: true, subtree: true});
>
>     function GetAvailableTradeObject(capacity) {
>         return Object.values(Game.town.objectDict).filter(tradeObj => tradeObj.logicType === 'Trade')
>             .find(tradeObj =>
>                   Game.unitsData[tradeObj.objData.UnitType].Capacity == capacity
>                   && !Game.town.tradesList.find(activeTrade => activeTrade.source.x == tradeObj.townX && activeTrade.source.z == tradeObj.townZ)
>                  )
>     }
>
>     function CloseWindows(elements, checkParent) {
>         for (let i=0, n=elements.length; i < n; i++) {
>             let el = checkParent ? elements[i].closest('.container') : elements[i];
>             let elVis = el.currentStyle ? el.currentStyle.visibility : getComputedStyle(el, null).visibility;
>             let elDis = el.currentStyle ? el.currentStyle.display : getComputedStyle(el, null).display;
>             if (!(elVis === 'hidden' || elDis === 'none')) {
>                 el.querySelector('.close-button') && el.querySelector('.close-button').click();
>             }
>         }
>     }
>
>     async function WaitForCompletion(selector) {
>         while (document.querySelector(selector) !== null) {
>             await new Promise( resolve => requestAnimationFrame(resolve) )
>         }
>         return document.querySelector(selector);
>     }
>
>     async function WaitForTradeLoad(targetTradeObj) {
>         return await new Promise(resolve => {
>             const waitForUpdate = setInterval(() => {
>                 let tradeUiObj = Game.app.root.findByName('TradeUi').script.trade.townObject;
>                 if (tradeUiObj && tradeUiObj.townX == targetTradeObj.townX && tradeUiObj.townZ == targetTradeObj.townZ && Game.app.root.findByName('TradeUi').script.trade.cityPaths[0].gasCost) {
>                     resolve('Loaded');
>                     clearInterval(waitForUpdate);
>                 };
>             }, 500);
>         });
>     }
>
>     async function WaitForElement(selector) {
>         while (document.querySelector(selector) === null) {
>             await new Promise( resolve => requestAnimationFrame(resolve) )
>         }
>         await new Promise(resolve => setTimeout(resolve, 1000));
>         return document.querySelector(selector);
>     }
>
>     async function CheckCrafts() {
>         let allTradeObjects = Object.values(Game.town.objectDict).filter(tradeObj => tradeObj.logicType === 'Trade');
>         for (let i=0, n=allTradeObjects.length; i < n; i++) {
>             if (allTradeObjects[i].logicObject.tapToCollectEntity.enabled) {
>                 allTradeObjects[i].logicObject.OnTapped();
>             }
>         }
>         if (Game.town.GetStoredCrafts()['Gasoline'] >= 1) {
>             for (let i=0, n=craftedItems.length; i < n; i++) {
>                 if (Game.town.GetStoredCrafts()[craftedItems[i].item] >= craftedItems[i].keepAmt + 10) {
>                     let targetTradeObj;
>                     if (Game.town.GetStoredCrafts()[craftedItems[i].item] >= 100 + craftedItems[i].keepAmt) {
>                         targetTradeObj = GetAvailableTradeObject(100);
>                     }
>                     if (!targetTradeObj && Game.town.GetStoredCrafts()[craftedItems[i].item] >= 50 + craftedItems[i].keepAmt && craftedItems[i].sellMin <= 50){
>                         targetTradeObj = GetAvailableTradeObject(50);
>                     }
>                     if (!targetTradeObj && Game.town.GetStoredCrafts()[craftedItems[i].item] >= 10 + craftedItems[i].keepAmt && craftedItems[i].sellMin <= 10){
>                         targetTradeObj = GetAvailableTradeObject(10);
>                     }
>                     if (targetTradeObj){
>                         CloseWindows(document.querySelectorAll('body > .container > .player-confirm .dialog-cell'), false);
>                         CloseWindows(document.querySelectorAll('.container > div:not(.hud):not(.player-confirm)'), true);
>                         Game.town.selectObject(targetTradeObj);
>                         Game.app.fire('SellClicked', {x: targetTradeObj.townX, z: targetTradeObj.townZ});
>                         await WaitForCompletion('.LoadingOrders');
>                         document.querySelector('#trade-craft-target [data-name="' + craftedItems[i].item + '"]').click();
>                         await WaitForTradeLoad(targetTradeObj);
>                         if (Game.town.GetStoredCrafts()['Gasoline'] >= Game.app.root.findByName('TradeUi').script.trade.cityPaths[0].gasCost) {
>                             document.querySelector('#destination-target .destination .sell-button').click();
>                             let tradeTimeout = setTimeout(function(){
> 								document.querySelector('.trade-connection .no').click();
> 							},10000);
>                             await WaitForCompletion('.trade-connection .compass');
> 							clearTimeout(tradeTimeout);
>                         } else {
>                             console.log('Whoops! You have run out of gas.');
>                             document.querySelector('#autosell-status .bank').textContent = 'ALERT: Out of gas!'
>                             document.querySelector('.container > .trade .close-button').click();
>                         }
>                     }
>                 }
>             }
>         } else {
>             console.log('Whoops! You have run out of gas.');
>             document.querySelector('#autosell-status .bank').textContent = 'ALERT: Out of gas!'
>         }
>         setTimeout(CheckCrafts, 5000);
>     }
>
>     async function ActivateAutoSell() {
>         let autoSellStatus = document.createElement('div');
>         autoSellStatus.id = 'autosell-status';
>         autoSellStatus.style.cssText = 'pointer-events: all; position: absolute; left: 50%; transform: translate(-50%, 0);';
>         autoSellStatus.addEventListener( 'click', function(){this.children[0].textContent = 'Auto-Sell Active';})
>         let autoSellContent = document.createElement('div');
>         autoSellContent.classList.add('bank');
>         autoSellContent.style.cssText = 'background-color: #fde7e3; padding-left: 10px; padding-right: 10px';
>         autoSellContent.textContent = 'Auto-Sell Active';
>         autoSellStatus.appendChild(autoSellContent);
>         await WaitForElement('.hud');
>         document.querySelector('.hud').prepend(autoSellStatus);
>         CheckCrafts();
>     }
>
> })();
> ```



### 三、**调试脚本**



脚本保存后，关闭设置页面，进入Town Star 游戏界面，**刷新**，这时脚本便**自动运行**了。

PS：每次**修改脚本**，都**要刷新**，否则脚本不工作。



### 四、**脚本修改**



![](https://3992198910-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMUnuyzmjRuPmBDiTwZ5i%2Fuploads%2FBb62BoSTJ4ecnmO4V2OI%2F912f18656979fc19e0ad0ff6b5d104f4_008idDKsly4gtzpmam37lj30u00grgpg.jpg?alt=media\&token=851f381c-8658-4fb4-806d-75710ef8cab3)

修改原理如下：\


单引号中单词是售卖的产品名；\


keepAmt：表示想保留多少库存；\


sellMin：表示想售卖多少产品；\


两个单词中间用“\_”隔开。



### 五、电子规划图，提高土地利用率



{% embed url="https://citadelofthewind.com/wp-content/visualizer/visualizer.html" %}

### 六、其他工具



{% embed url="https://gala-analytics.herokuapp.com/townstar" %}
