Doing RoomVisual Logic Just For the Room I'm Looking At?



  • @artch Sorry, the only reason I called it "fake" was because the last time I looked at the engine code, that is what I thought it was called, and I wanted to differentiate it from the browser console.


  • Dev Team

    @gankdalf It is called fake because it's not real JS console interface. The game client has no direct access to it, it sends console commands in the same way as it commits scripts.

    Also, you may have multiple tabs open with different rooms, and engine code should not be concerned with these semantics.



  • @artch That is exactly to what I was referring though. It is a bit of a make-shift solution, but lets say I had this script for my in-game code.

    const _rooms = [];
    const _lruRooms = {};
    const ROOM_LIMIT = 3;
    
    global.viewedRoom = function(roomName) {
    	if (_room.indexOf(roomName)) {
    		_rooms.push(roomName);
    	}
    }
    
    global.visibleRooms = () => {
    	return Object.keys(_lruRooms);
    }
    
    export function loop() {
    	for (const roomName of _rooms) {
    		_lruRooms[roomName] = Game.time;
    	}
    	while (Object.keys(_lruRooms).length > ROOM_LIMIT) {
    		let oldest;
    		for (const roomName in _lruRooms) {
    			if (_lruRooms.hasOwnProperty(roomName)) {
    				if (!oldest && _lruRooms[roomName] < _lruRooms[oldest]) {
    					oldest = roomName;
    				}
    			}
    		}
    		delete _lruRooms[oldest];
    	}
    }
    

    If the OnRoomLoad event (or whatever the event is) for the client used the client -> console end-point to fire off a console command of viewedRoom("roomName");, it would be possible to limit the number of rooms I was creating visuals for by only creating them for at most the last 3 rooms I opened.

    As you have stated, this may not be something that should be implemented into the default client, but I am looking through the existing TamperMonkey scripts trying to find the OnRoomLoad and PostToConsole event and end-point, so that I can try it out.



  • I created an in-game script that, in conjunction with a PR for backend-local, would provide you a solution. you'd just have to run something in your console to catch each client. I feel like this would be your best bet. Have fun 🙂


  • Dev Team

    Posting a console command on every room load is not encouraged. If each player does so, it would significantly affect backend performance. We reserve the right to rate limit your console if we detect any automatic activity outside of our Auth Tokens system.



  • @artch So if SemperRabbit's script were changed to use an actual post to the API end-point with an auth token, that would be acceptable?


  • Dev Team

    @gankdalf Yes, since Auth Tokens are rate limited. Note that the limit for POST /api/user/console is 360 requests per hour, see here.



  • @artch, I absolutely understand your concern about server load. Would it be ok to edit it to only push to console when the user changes rooms, not every tick? And if so, is there a better way than the following? The only current way I can see, is still using angular.element($('body')).injector().get('Connection').onRoomChange() and tracking URL changes or changes to the roomName value in the room scope. Would that be acceptable to the devs? I doubt many users are switching rooms quickly enough to overburden the servers, and fewer still would actually be using a script like this.

    Also, I think that the auth token support could be easily included in these scripts. I'll start using that in all my future endeavours.



  • @artch Cool. I don't see myself changing rooms more than 20-50 times an hour, and that would only be if I am sitting there exploring for the whole hour, so that should be well within the rate limit.

    👆


  • EDIT - this snippet only works in IVM. To support non-IVM, the rooms cache would need placed into Memory

    So at the bottom of this post is an example script that uses the user/console API to create this functionality. It will inject the functionality into your running client whenever your global resets, or whenever you run the console command injectViewHistory();. It should never inject the functionality into your client more than once, even if the global resets. It will put an undefined into your console each time you change rooms. See this forum post for details on that functionality getting changed: https://screeps.com/forum/topic/2182/allow-third-party-software-to-not-spam-the-in-game-console

    Note that this example script does not use the correct auth token end-point, so if you spam room changes rapidly, you could get rate-limited like @artch already stated. This call is very easy to change to a post with your auth token, but I did not use the auth token version in this prototype.

    You must save the script below as a module named viewhistory. Then, in your main module you can then use it like this:

    main.js

    var viewhistory = require('viewhistory');
    viewhistory.inject();
    
    module.exports.loop = function() {
        console.log(viewhistory.rooms());
    }
    

    viewhistory.js

    let injected = false;
    const _rooms = {};
    const ROOM_LIMIT = 3;
    
    global.injectViewHistory = function() {
        injected = false; 
        inject();
    }
    
    function inject(){
        if(!injected) {
            injected = true;
            var output = `<SPAN>Trying to inject View History code!</SPAN>
            <SCRIPT>
            if (typeof watching === "undefined") {
                var watching = false;
                let lastRoom = "sim";
                
                function attach() {
                    if(!$('section.game').length) return setTimeout(attach,100);
                
                    let gameScope = angular.element($('body')).scope();
                    if (gameScope && !watching) {
                        watching = true;
                        gameScope.$watch('Loader.loadingCnt', (n) => {
                            if (n === 0) {
                                let roomScope = angular.element($('section.room')).scope();
                                if (roomScope) {
                                    let Api = angular.element($('section.game')).injector().get('Api');
                                    let room = roomScope.Room;
                                    if (room) {
                                        if (lastRoom !== room.roomName) {
                                            lastRoom = room.roomName;
                                            console.log("viewed " + room.roomName);
                                            /* should be replaced with an auth token post to user/console instead of injected */
                                            Api.post('user/console',{
                                                expression: "viewedRoom('" + room.roomName + "');",
                                                shard: room.shardName
                                            });
                                        }
                                    }
                                }
                            }
                        });
                    }
                }
                
                $(function () {
                    setTimeout(attach);
                });
            }
            </SCRIPT>`
    	    console.log(output.replace(/(\r\n|\n|\r)\t+|(\r\n|\n|\r) +|(\r\n|\n|\r)/gm, ''));
        }
    }
    
    function rooms() {
    	return Object.keys(_rooms);
    }
    
    global.viewedRoom = function(roomName) {
    	_rooms[roomName] = Game.time;
    	let keys = Object.keys(_rooms).length;
    	while (keys > ROOM_LIMIT) {
    		let oldest;
    		for (const roomName in _rooms) {
    			if (_rooms.hasOwnProperty(roomName)) {
    				if (!oldest || _rooms[roomName] < _rooms[oldest]) {
    					oldest = roomName;
    				}
    			}
    		}
    		keys--;
    		delete _rooms[oldest];
    	}
    }
    
    module.exports = {
        inject,
        rooms
    };