Good way to handle ability usage within a multiplayer game? - actionscript-3

I have written a client<>server based multiplayer game and have finished the basics (it's a flash client written in actionscript 3). The next step will be implementing a system which will allow me to easily add abilities to the game.
The problem here is that an ability can do so many things. Eg. player A uses ability "repair" on player B, the following will have to happen:
Player A sends message to server informing about the action
Player A is now showing a "repair casted" animation on its own ship
The server has to inform all players near that ship A now has the "repair casted" animation
The server has to increase the "health" of player B because it has been repaired by player A
The server has to inform all nearby players that player B now has a different health value
The server has to inform all nearby players that player B should show the "being repaired" animation
That's just an example, a lot of things have to happen for 1 simple ability. I could go into every object and add lines of code just for this spell. However, that would become a mess when I need to add a lot (~50) abilities. Also notice that some abilities do whole other things than others, some will have to show animations, some won't. Some will have to damage, some will have to increase statistics, etc.
So, how is such "ability system" usually handled both client and server side?

On client side never do a thing without the consent of the server. So client should only send the request of an ability and an optional target or some value. Also client should have to listen server for any commands. On the other hand, server should implement a function or classes (whatever you feel comfortable) to control ability actions. Whenever a client requests an ability to cast ability function should be called. As first task, function should check prerequisites. If they fail, client should be notified. Otherwise, a use repair ability ok signal should be passed to client. Then the function finds and send required commands to any nearby ships while updating their statuses. This method will guarantee that abilities cannot be abused by clients.
To understand it check the following, this is for illustration of the method, so dont tell me its not good to use so many ifs.
Client
function massRepair() {
server.send("USE mass_repair;"); //no decisions in here
}
function recive(command, target) {
if(command=="USE mass_repair OK")
massRepairAnim.gotoAndPlay(0);
if(command=="ANIM mass_repair") {
massRepairAnim.setTarget(target);
massRepairAnim.gotoAndPlay(0);
}
if(command==/SET ENERGY [0-9]/) {
value=getValue(command);
setEnergy(value);
}
if(command==/SET HEALTH [0-9]/) {
value=getValue(command);
setHealth(value);
}
if(command=="ANIM self_repair") {
selfRepairAnim.gotoAndPlay(0);
}
if(command=="USE mass_repair FAIL NOENERGY") {
display("Not enough energy to use the skill");
}
}
somebutton.click=function() { massRepair(); }
Server
function userMassRepair(Ship owner) {
if(owner.energy<30) {
send(owner, "USE mass_repair FAIL NOENERGY");
return false;
}
owner.energy-=30;
send(owner, "SET ENERGY "+owner.energy); //every data sent is absolute
send(owner, "USE mass_repair OK");
foreach(ship in owner.shipsInRange(100)) {
if(owner.ally(ship)) {
ship.health+=10;
ship.send("SET HEALTH " + ship.health);
ship.send("ANIM mass_repair "+owner.position);
ship.send("ANIM self_repair");
}
}
}

It looks like a lot of things, but it's really just a permutation of several standard components. Most actions will consist of changes to intrinsic properties and some cosmetics to be shown on the client, and there are only a handful of different ways to do those.
You need something like the following:
a standard way of sending actions to the server, which allows for convenient extraction of arbitrary parameters (as each action may have some very specific arguments)
a message type from server to clients to start playing a specified animation on a specified ship
a message type from server to clients increasing the health value on a ship
Then your server code for the action you mention looks like this pseudocode:
if (message.type == REPAIR)
{
repairer = message.actor;
repaired = message.target;
// Insert verification code here to check that the 2 ships are close enough,
// that the repairer is entitled to perform this action, etc
// Verification has passed: do the work
broadcastToAllClients(new Message(type=PLAY_ANIM, target=repairer, value=REPAIR_CASTED));
broadcastToAllClients(new Message(type=CHANGE_PROPERTY, target=repaired, proerty=HEALTH, value=5)); // 5 is the amount
broadcastToAllClients(new Message(type=PLAY_ANIM, target=repaired, value=BEING_REPAIRED));
}
That is very easy to extend to any number of actions, and will just require a few extra message types to accommodate the different things that can change or can be displayed.

Related

My messages are delivered out of flow sequence order and how do I compensate?

I wish to use Twilio in the context of an adventure game. As the gamer (Geocacher) progresses on an actual treasure (cache) hunt, clues are given by text when certain code words or numbers are entered as part of the thread. I am very comfortable creating the flow in Studio and have a working flow that is debugged and ready to go.
Not so fast, grasshopper! Strange things started to happen when beta testing the flow. Basically texts that show as being sent arrive to the user out of sequence in the thread. The SM logs show everything is working correctly (message sent) but, what I call Zombie messages arrive to the user after a previous message has arrived. The Zombies are legitimate messages from the Flow but out of the correct sequence and that makes the thread unusable for my purposes.
I learned too late in my "programming" that Twilio states, "If you send multiple SMS messages to the same user in a short time, Twilio cannot guarantee that those messages will arrive in the order that you sent them." Ugh!
So , I started with the Help Techs at Twillio and one solution is to create a subflow that basically is inserted after a Send Message Widget. This sub flow basically Fetches the message via the SMS SID to check for SMS status. If status is "delivered", we can safely say the message has been received by the recipient and then permit the next message in the flow.
That sound great but I am not a programmer and will never be able to integrate the suggested code much less debug it when things don't work. There might be many other approaches that you guys can suggest. The task is 1.) Send a message, 2.) Run a subflow that checks for message delivery, 3.) send the next message in the sequence.
I need to move on to implementation and this type of sub flow is out of my wheelhouse. I am willing to pay for programming help.
I can post the JSON code that was created as a straw man but have no idea how to use it and if it is the optimum solution if that is of help. It would seem that a lot of folks experience this issue and would like a solution. A nice tight JSON subflow with directions on how to insert would seem to be a necessary part of the Widget toolkit provided by Twillio in Studio.
Please Help Me! =)
As you stated, the delivery of the message cannot be guaranteed. Checking the status of the sent message is the most reliable, using a subflow, a Twilio Function, or a combination. Just keep in mind that Twilio Functions have a 10s execution time limit. I don't expect delivering the SMS will take longer than 10s is most cases. If you're worried about edge cases, you'd have to loop the check for the status multiple times. I'll share a proof of concept later for this.
An easier way, but it still doesn't guarantee delivery order, would be to add some delay between each message. There's no built-in delay widget, but here's code on how to create a Twilio Function to add delays, up to 10s.
A more hacky way to implement delays without having to use this Twilio Function, is to use the Send & Wait For Reply Widget and configure the "Stop Gathering After" property to the amount of delay you'd like to add. If the user responds, connect to the next widget, if they don't also connect to the widget.
As mentioned earlier, here's th Subflow + Function proof of concept I hacked together:
First, create a Twilio Functions Service, in the service create two functions:
/delay:
// Helper function for quickly adding await-able "pauses" to JavaScript
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
exports.handler = async (context, event, callback) => {
// A custom delay value could be passed to the Function, either via
// request parameters or by the Run Function Widget
// Default to a 5 second delay
const delay = event.delay || 5000;
// Pause Function for the specified number of ms
await sleep(delay);
// Once the delay has passed, return a success message, TwiML, or
// any other content to whatever invoked this Function.
return callback(null, `Timer up: ${delay}ms`);
};
/get-message:
exports.handler = function(context, event, callback) {
const messageSid = event.message_sid,
client = context.getTwilioClient();
if(!event.message_sid) throw "message_sid parameter is required.";
client.messages(messageSid)
.fetch()
.then(message => callback(null, message))
.catch((error) => {
console.error(error);
return callback(error);
});
};
Then, create a Studio Flow named something like "Send and Wait until Delivered".
In this flow, you send the message, grabbing the message body passed in from the parent flow, {{trigger.parent.parameters.message_body}}.
Then, you run the /get-message Function, and check the message status.
If delivered, set status variable to delivered. This variable will be passed back to the parent flow. If any of these accepted,queued,sending,sent, then the message is still in route, so wait a second using the /delay function, then loop back to the /get-message function.
If any other status, it is assumed there's something wrong and status is set to error.
Now you can create your parent flow where you call the subflow, specifying the message_body parameter. Then you can check the status variable for the subflow, whether it is 'delivered' or 'error'.
You can find the export for the subflow and the parent flow in this GitHub Gist. You can import it and it could be useful as a reference.
Personally, I'd add the /delay function, and use that after every message, adding a couple of seconds delay. I'd personally assume the delay adds enough buffer for no zombie messages to appear.
Note: The code, proof of concept, and advice is offered as is without liability to me or Twilio. It is not tested against a production workload, so make sure you test this thoroughly for your use case!

Questions on extending GAS spreadsheet usefulness

I would like to offer the opportunity to view output from the same data, in a spreadsheet, TBA sidebar and, ideally another type of HTML window for output created, for example, with a JavaScript Library like THREE.
The non Google version I made is a web page with iframes that can be resized, dragged and opened/closed and, most importantly, their content shares the same record object in the top window. So, I believe, perhaps naively, something similar could be made an option inside this established and popular application.
At the very least, the TBA trial has shown me it useful to view and manipulate information from either sheet or TBA. The facility to navigate large building projects, clone rooms and floors, and combine JSON records (stored in depositories like myjson) for collaborative work is particularly inspiring for me.
I have tried using the sidebar for different HTML files, but the fact only one stays open is not very useful, and frankly, sharing record objects is still beyond me. So that is the main question. Whether Google people would consider an extra window type is probably a bit ambitious, but I think worth asking.
You can't maintain a global variable across calls to HtmlService. When you fire off an HtmlService instance, which runs in the browser, the server side code that launched it exits.
From that point control is client side, in the HtmlService code. If you then launch a server side function (using google.script.run from client side), a new instance of the server side script is launched, with no memory of the previous instance - which means that any global variables are re-initialized.
There are a number of techniques for peristing values across calls.
The simplest one of course is to pass it to the htmlservice in the first place, then to pass it back to server side as an argument to google.script.run.
Another is to use property service to hold your values, and they will still be there when you go back, but there is a 9k maximum entry size
If you need more space, then the cache service can hold 100k in a single entry and you can use that in the same way (although there is a slight chance it will be cleaned away -- although it's never happened for me)
If you need even more space, there are techniques for compressing and/or spreading a single object across several cache entries - as documented here http://ramblings.mcpher.com/Home/excelquirks/gassnips/squuezer. This same method supports Google Drive, or Google cloud storage if you need to persist data even longer
Of course you can't pass non-stringifiable objects like functions and so on, but you can postpone their evaluation and allow the initialized server side script to evaulate them, and even share the same code between server, client or across projects.
Some techniques for that are described in these articles
http://ramblings.mcpher.com/Home/excelquirks/gassnips/nonstringify
http://ramblings.mcpher.com/Home/excelquirks/gassnips/htmltemplateresuse
However in your specific example, it seems that the global data you want is fetched from an external api call. Why not just retrieve it client side in any case ? If you need to do something with it server side, then pass it to the server using google.script.run.
window.open and window.postMessage() solved both the problems I described above.
I hope you will be assured from the screenshot and code that the usefulness of Google sheets can be extended for the common good. At the core is the two methods for inputting, copying and reviewing textual data - spreadsheet for a slice through a set of data, and TBA for navigation of associations in the Trail (x axis) and Branches (y axis), and for working on Aspects (z axis) of the current selection that require attention, in collaborations, from different interests.
So, for example, a nurse would find TBA useful for recording many aspects of an examination of a patient, whereas a pharmacist might find a spreadsheet more useful for stock control. Both record their data in a common object I call 'nset' (hierarchy of named sets), saved in the cloud and available for distribution in collaborative activities.
TBA is also useful for cloning large sets of records. For example, one room, complete with furniture can be replicated on one floor, then that floor, complete with rooms can be replicated for a complete tower.
Being able to maintain parallel nset objects in multiple monitor windows by postMessage means unrivalled opportunities to display the same data in different forms of multimedia, including interactive animation, augmented reality, CNC machine instruction, IOT controls ...
Here is the related code:
From the TBA in sidebar:
window.addEventListener("message", receiveMessage, false);
function openMonitor(nset){
var params = [
'height=400',
'width=400'
].join(',');
let file = 'http://glasier.hk/blazer/model.html';
popup = window.open(file,'popup_window', params);
popup.moveTo(100,100);
}
var popup;
function receiveMessage(event) {
let ed,nb;
ed = event.data;
nb = typeof ed === "string"? ed : nb[0];
switch(nb){
case "Post":
console.log("Post");
popup.postMessage(["Refreshing nset",nset], "http:glasier.hk");
break;
}
}
function importNset(){
google.script.run
.withSuccessHandler(function (code) {
root = '1grsin';
trial = 'msm4r';
orig = 'ozs29';
code = orig;
path = "https://api.myjson.com/bins/"+code;
$.get(path)
.done((data, textStatus, jqXHR) => {
nset = data;
openMonitor(nset);
cfig = nset.cfig;
start();
})
})
.sendCode();
}
From the popup window:
$(document).ready(function(){
name = $(window).attr("name");
if(name === "workshop"){
tgt = opener.location.href;
}
else{
tgt = "https://n-rxnikgfd6bqtnglngjmbaz3j2p7cbcqce3dihry-0lu-script.googleusercontent.com"
}
$("#notice").html(tgt);
opener.postMessage("Post",tgt);
$(window).on("resize",function(){
location.reload();
})
})
}
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
let ed,nb;
ed = event.data;
nb = typeof ed === "string"? ed : ed[0];
switch(nb){
case "Post": popup.postMessage(["nset" +nset], "*"); break;
default :
src = event.origin;
notice = [ed[0]," from ",src ];
console.log(notice);
// $("#notice").html(notice).show();
nset = ed[1];
cfig = nset.cfig;
reloader(src);
}
}
I should explain that the html part of the sidebar was built on a localhost workshop, with all styles and scripts compiled into a single file for pasting in a sidebar html file. The workshop also is available online. The Google target is provided by event.origin in postMessage. This would have to be issued to anyone wishing to make different monitors. For now I have just made the 3D modelling monitor with Three.js.
I think, after much research and questioning around here, this should be the proper answer.
The best way to implement global variables in GAS is through userproperties or script properties.https://developers.google.com/apps-script/reference/properties/properties-service. If you'd rather deal with just one, write them to an object and then json.stringify the object (and json.parse to get it back).

Restrict feathers service method to user for external but allow any queries for internal calls

I want to restrict calls to a Feathers service method for externals calls with associateCurrentUser.
I also want to allow the server to call this service method without restricting it.
The use case is that through this service then clients use a lock table, all clients can see all locks, and occasionally the server should clear out abandoned rows in this table. Row abandonment can happen on network failures etc. When the server removes data then the normal Feathers remove events should be emitted to the clients.
I would imagine that this should be a mix of associateCurrentUser and disallow hooks but I can't even begin to experiment with this as I don't see how it would be put together.
How would one implement this, please?
Update:
I found this answer User's permissions in feathers.js API from Daff which implies that if the hook's context.params.provider is null then the call is internal, otherwise external. Can anyone confirm if this is really so in all cases, please?
It seems to be so from my own tests but I don't know if there are any special cases out there that might come and bite me down the line.
If the call is external params.provider will be set to the transport that has been used (currently either rest, socketio or primus, documented here, here and here).
If called internally on the server there is not really any magic. It will be whatever you pass as params. If you pass nothing it will be undefined if you pass (or merge with) hook.params in a hook it will be the same as what the original method was called with.
// `params` is an empty object so `params.provider` will be `undefined`
app.service('messages').find({})
// `params.provider` will be `server`
app.service('messages').find({ provider: 'server' })
// `params.provider` will be whatever the original hook was called with
function(hook) {
hook.app.service('otherservice').find(hook.params);
}

How to find out the availability status of a Web API from a Windows Store application

I have a Line-of-Business (LoB) Windows 8.1 Store application I developed for a client. The client side-loads it on several Windows 10 tablets. They use it in an environment where WiFi is spotty at best and they would like to get some sort of notification inside the app, regardless of what page they are on, notification that will let them know that they've lost connectivity to the network. I have created a method on my Web API that is not hitting the repository (database). Instead, it quickly returns some static information regarding my Web API, such as version, date and time of the invocation and some trademark stuff that I'm required to return. I thought of calling this method at precise intervals of time and when there's no response, assume that the Web API connectivity is lost. In my main page, the first one displayed when the application is started, I have the following stuff in the constructor of my view model:
_webApiStatusTimer = new DispatcherTimer();
_webApiStatusTimer.Tick += OnCheckWebApiStatusEvent;
_webApiStatusTimer.Interval = new TimeSpan(0, 0, 30);
_webApiStatusTimer.Start();
Then, the event handler is implemented like this:
private async void OnCheckWebApiStatusEvent(object sender, object e)
{
// stop the timer
_webApiStatusTimer.Stop();
// refresh the search
var webApiInfo = await _webApiClient.GetWebApiInfo();
// add all returned records in the list
if (webApiInfo == null)
{
var messageDialog = new MessageDialog(#"The application has lost connection with the back-end Web API!");
await messageDialog.ShowAsync();
// restart the timer
_webApiStatusTimer.Start();
}
}
When the Web API connection is lost, I get a nice popup message that informs me that the Web API is no longer available. The problem I have is that after a while, especially if I navigate away from the first page but not necessary, I get an UnauthorizedAccessException in my application.
I use the DispatcherTimer since my understanding is that this is compatible with
UI threads, but obviously, I still do something wrong. Anyone cares to set me on the right path?
Also, if you did something similar and found a much better approach, I'd love to hear about your solution.
Thanks in advance,
Eddie
First, If you are using Windows Store Apps, then you could possibly use a Background task to check poll for the status of the web api instead of putting this responsibility on your view model, its not the viewmodels concern
Second, if you are connecting from your Windows store app to your API then one successful authentication/ authorization for the first time, how and where do you store the token (assuming you are using token authentication). If you are (and ideally you should), is there a timer that you start which is set to the token expiration time? Is your local storage getting flushed somehow and loosing the aurthorization data?
Need more information.

GUI for Akka application

I made a small application in Akka that has a hierarchy of actors (there is an ActorA that has several ActorB actors, and these ActorB actors have several ActorC actors). Now I'd like to add a small UI. This UI doesn't have any buttons, but just some labels that are modified when the ActorC actors receive certain messages from other actors. The problem that I have is that I must create a label for each ActorC actor, but the number (of ActorC actors) is not always the same, so I must create the ActorC actors in the first place, and then I must create the labels. I searched for tutorials on Scala Swing, but i just found old documentation or simple examples that are not useful in my case (something like press a button to do something). I even saw that several people used Java Swing in their Akka applications. I found a tool called Kamon, but i need to create an interface, not just monitoring my actors. Is there someone that can help me?
Swing is deprecated in favor of JavaFX. That said, JavaFX allows you to create a "rich client" application. It sounds like too heavy of a solution for what you want.
Perhaps a simple web UI? Have you looked at playframework.com? Your interface would just be some web pages that can talk to the actors. Play and Akka work well together, and it's pretty easy to get started with Play using available tutorials and templates.
There are two aspects to your design. The first aspect is the communication between your UI and your working actors. The second is the operation of your UI.
A scenario might be that your UI starts up and fires up a Swing EDT thread to display your status window. An actor is also created and it reaches out to your Actor A and says "ready to go". Actor A forwards the message to all Actor Bs, which forward it to the Actor Cs. Each Actor C then registers itself with the UI by sending a message "I am here" and "this is my status".
def receive = {
...
case ReadyToGo =>
myMonitor = sender
sender ! "ready"
}
The UI-Actor takes each of the registration messages and maps it to a label, creating it if needed. As each Actor C changes its state, it sends a new message to UI-Actor, saying "I am still here" and "this is my status".
The UI-Actor is the go-between the Akka world and the UI. This is the tricky part, since the UI-Actor runs on a different thread than the Swing event-dispatch-thread. It must put a task on the Swing queue for processing by the Swing thread in a thread-safe manner. Some rough code:
// assume labelMap is a map from ActorRef to Label, ordered by insertion
def receive = {
case IAmHere(status) =>
val actorC = sender
if ( ! (labelMap contains actorA) ) {
addStatusLabel(actorC, status)
}
case IAmStillHere(status) =>
updateStatusLabel(actorC, status)
}
def addStatusLabel(actorC: ActorRef, status: String) = {
Swing.onEDT {
labelMap(actorC) = new Label(status)
// recalculate the panel size
// repaint
}
}
def updateStatusLabel(actorC: ActorRef, status: String) = {
Swing.onEDT {
labelMap(actorC).text = status
// repaint
}
}
I leave it up to you to lay out the UI, the container, whether statuses are Strings or not, how to handle Actor Cs that go missing, etc.