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



  • Something like a RoomVisual.callback you could register to that only runs for the room you are viewing would be pretty cool.



  • One solution would be to allow only one RoomVisual instance and give it an arry with all rooms that are currently subscribed, since it could be multiple rooms.

    You can emulate that with a "info flag" as workaround



  • @tigga @artch

    Tigga nails it.

    I want to:

    1. Foreach room that is visible to me in the GUI
    2. Do a bunch of calculations that might be expensive
    3. Present the results as a RoomVisual

    I don't want to:

    1. Do a bunch of calculations that might be expensive for every room in my empire
    2. Present the results for all 120 rooms in a RoomVisual and maybe hit the 500kb limit, especially since I am only looking at 1 room at a time

    The best solution I have to far is I have a console command that turns on my viz layer for a specific room (it takes the roomName as an arg). However, this is fairly tedious if I need to type in a command for each room I want to look at when I'm debugging. I want Screeps to give me that roomName as an arg somewhere. What rooms are visible to my user in the GUI?


  • Dev Team

    Unfortunately, that is not technically possible, since it would require syncing game engine state and frontend client state. There is no such communication mechanism in the game, and implementing it would break the current architecture.



  • Even something as simple as firing something similar to a console command automatically with the room name as a parameter when a room is opened by a user would serve the purpose in a make-shift way.


  • Dev Team

    @gankdalf Game engine code is not aware of anything happening in the game client, there is no connection between them in our decoupled architecture.



  • But the client has its (fake) console. I mean even just having the client itself run a predefined console command when it opens a room would serve the purpose in a way.


  • Dev Team

    @gankdalf The console is not fake, it's a part of the game engine and is being executed in the same runtime loop as your script.



  • @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
    };