PageSourceSearch

https://vc64web.github.io/js/vc64_action_script.js

js vc64web.github.io collected 2026-10-03 10:08:01 UTC 20,024 bytes, 756 lines download raw bytes

1const sleep = (milliseconds) => {
2  return new Promise(resolve => setTimeout(resolve, milliseconds));
3}
4
5var map_of_running_scripts = [];
6function get_running_script(id)
7{
8    let running_script=map_of_running_scripts[id];
9    if(running_script == null)
10    {
11        running_script = {
12            id: id,
13            running: false,
14            stop_request: false,
15            action_button_released: false
16         };
17        map_of_running_scripts[id]=running_script;
18    }
19    return running_script;
20}
21
22var execute_script = function(id, script_lang, action_script) {    
23    let running_script=get_running_script(id);
24    if(running_script.running)
25    {
26        //alert("action script with "+id+" is still running, double execution is prevented... requesting stop");
27        running_script.stop_request=true;
28    }
29    else
30    {
31        running_script.running=true;
32        running_script.stop_request=false;
33
34        setTimeout(async function() { 
35            let button_element=$('#ck'+id);
36            button_element.css("background-color", "var(--red)");
37            try{
38              await parse_script(script_lang, action_script, true, id);
39            }
40            finally
41            {
42              if(button_element.length >0 && button_element[0].parentElement == null)
43              {//if the button_element is decoupled because button was redrawn as a new DOM instance
44                  button_element=$('#ck'+id); //then get the new dom element
45              }
46              button_element.css("background-color", "");
47              
48              let running_script=get_running_script(id);
49              running_script.running = false;
50              running_script.stop_request=false;
51            }
52        });
53    }
54}
55
56function stop_all_scripts()
57{
58    for(id in map_of_running_scripts)
59    {
60        let action_button = custom_keys.find(el => el.id == id); 
61        if(action_button === undefined ||
62           action_button.auto_started !== undefined) 
63        {//skip auto started scripts
64            continue;
65        }
66
67        let running_script=get_running_script(id);
68        running_script.stop_request=true;
69    }
70}
71
72function not_stopped(id)
73{
74    return id<0 ? true : get_running_script(id).stop_request == false;
75}
76
77async function parse_script(script_lang, action_script, execute = false, execution_id = -1) {
78    action_script = action_script.trim();
79    if(action_script.length==0)
80    {
81        $('#action_button_syntax_error').html('you have to enter at least one action ...');
82        return false;
83    }
84    if(script_lang == 'javascript')
85    {
86        var valid = true;
87        var js_script=action_script;
88        var js_script_function;
89        let AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
90        try {            
91            js_script_function=new AsyncFunction(`let this_id=${execution_id};let still_active=function(){return not_stopped(this_id);};${js_script}`);
92        } catch (error) {
93            valid=false;
94            if(execute==false)
95            {
96                $('#action_button_syntax_error').html(error.message);
97            }
98        }
99        if(execute)
100        {
101            await js_script_function();
102        }
103    }
104    else
105    {
106       valid = await execute_action_sequence_script(action_script, execute, execution_id);
107    }
108    return valid;
109};
110
111async function action(cmd, execute=true)
112{
113    await execute_action_sequence_script(cmd, execute);
114}
115
116
117async function execute_action_sequence_script(action_script, execute=false, execution_id=-1)
118{
119    action_script=action_script.replace(/[{]/g,'{=>')
120    action_script=action_script.replace(/[}]/g,'=>}')
121    var cmd_sequence = action_script.split('=>');
122    var valid = true;
123    var error_message = "";
124
125    var pc=0;
126    var pc_loop_begin=[];
127    var lc=[];
128    var loop_depth=0;
129
130    while (pc < cmd_sequence.length && not_stopped(execution_id)) {
131        var cmd = cmd_sequence[pc].trim();
132        pc++;
133
134        if(cmd.match(/^loop[0-9]+[{]$/) != null)
135        {
136            loop_depth++;
137            lc[loop_depth]=execute ? parseInt(cmd.match(/[0-9]+/)) : 1;
138            pc_loop_begin[loop_depth]=pc;
139        }
140        else if(cmd.length == 1 && cmd =='}')
141        {
142            lc[loop_depth]--;
143            if(lc[loop_depth]>0)
144            {
145                pc=pc_loop_begin[loop_depth];
146            }
147            else
148            {
149                loop_depth--;
150                if(loop_depth<0)
151                {
152                    error_message="too many closing loop bracket at pc="+pc;
153                    valid=false;
154                    break;
155                }
156            }
157        }
158        else if(await execute_single_action(cmd, execute, execution_id)==true)
159        {            
160        }
161        else
162        {
163            error_message='unknown command "'+cmd+'" at pc=' +pc;
164            valid = false;
165            break;
166        }
167    }
168
169    if(loop_depth>0)
170    {
171        error_message="missing closing loop "+pc_loop_begin+" brackets";
172        valid=false;
173    }
174
175    if(execute == false)
176    {
177        $('#action_button_syntax_error').html(error_message);
178    }
179    return valid;
180}
181
182
183async function execute_single_action(cmd, execute=true, execution_id=-1)
184{
185    cmd=cmd.trim();
186    var valid = true;
187    var joy_cmd_tokens=null;
188    if(cmd.match(/^'.+?'$/sm) != null)
189    {
190        if(execute)
191        {
192            var chars = cmd.substring(1,cmd.length-1);//.split("");
193            var time_to_emit_next_char = 100;
194//            emit_string(chars,0,time_to_emit_next_char);
195            wasm_auto_type(chars);
196            //blocking execution of action script and wait for all keys emitted
197            await sleep(time_to_emit_next_char*chars.length);                  
198        }
199    }
200    else if(cmd == 'pause')
201    {
202        if(execute)
203        {
204            if(is_running())
205            {
206                app.button_run_click(); 
207            } 
208        }
209    
210    }
211    else if(cmd == 'run')
212    {
213        if(execute)
214        {
215            if(!is_running())
216            {
217                app.button_run_click();   
218            } 
219        }
220    }
221    else if(cmd == 'toggle_run')
222    {
223        if(execute)
224        {
225            app.button_run_click();  
226        }
227    }
228    else if(cmd == 'toggle_warp')
229    {
230        if(execute)
231        {//0==auto, 1==never, 2==always
232            let mode = wasm_get_config("OPT_C64_WARP_MODE");
233            if(mode <= 1) auto_or_never_warp_mode = mode ==0 ?"WARP_AUTO":"WARP_NEVER";
234            wasm_configure('OPT_C64_WARP_MODE', mode<=1 ? 2/*'WARP_ALWAYS'*/: auto_or_never_warp_mode==="WARP_AUTO"?0:1);
235        }
236    }
237    else if(cmd == 'toggle_speed')
238    {
239        if(execute)
240        {
241            $('#button_speed_toggle').click();
242        }
243    }    
244    else if(cmd.match(/^[0-9]+ms$/) != null)
245    {
246        if(execute)
247        {
248            await sleep(parseInt(cmd.match(/[0-9]+/)));                 
249        }
250    }
251    else if(cmd == 'take_snapshot')
252    {
253        if(execute)
254        {
255            $('#button_take_snapshot').click();
256        }
257    }
258    else if(cmd == 'keyboard')
259    {
260        if(execute)
261        {
262            app.button_keyboard_click();
263        }
264    }
265    else if(cmd == 'menubar')
266    {
267        if(execute)
268        {
269            app.button_show_menu_click();
270        }
271    }
272    else if(cmd == 'datasette_play')
273    {
274        if(execute)
275        {
276            wasm_press_play();
277        }
278    }
279    else if(cmd == 'datasette_stop')
280    {
281        if(execute)
282        {
283            wasm_press_stop();
284        }
285    }
286    else if(cmd == 'datasette_rewind')
287    {
288        if(execute)
289        {
290            wasm_rewind();
291        }
292    }
293    else if(cmd == 'clipboard_paste')
294    {
295        if(execute)
296        {
297            let cliptext = await navigator.clipboard.readText();
298            action(`'${cliptext.toLowerCase()}'`);
299        }
300    }
301    else if(cmd == 'fullscreen')
302    {
303        if(execute)
304        {
305            app.button_fullscreen_click();
306        }
307    }
308    else if(cmd == 'restore_last_snapshot')
309    {
310        if(execute)
311        {
312            await load_last_snapshot();
313        }
314    }
315    else if(cmd == 'swap_joystick')
316    {
317        if(execute)
318        {
319            var port1_value=port1;
320            port1=port2;
321            port2=port1_value;
322            port1_value= $('#port1').val();
323            $('#port1').val($('#port2').val());
324            $('#port2').val(port1_value);
325        }
326    }
327    else if(
328        (
329            joy_cmd_tokens=cmd.match(/^j([12])(fire|up|down|right|left)([01])$/)
330        )
331        != null
332    )
333    {
334        if(execute)
335        {
336            execute_joystick_script(joy_cmd_tokens);
337        }
338    }
339    else if(translateKey2(cmd,cmd) !== undefined)
340    {//one single char
341        if(execute)
342        {            
343            emit_key(cmd,0,100); 
344        }
345    }
346    else if(
347        cmd.startsWith("press")
348        &&
349        translateKey2(cmd.replace("press",""),cmd.replace("press","")) !== undefined        
350    )
351    {
352        if(execute)
353        {            
354            press_key(cmd.replace("press","")); 
355        }
356    }
357    else if(
358        cmd.startsWith("release")
359        &&
360        translateKey2(cmd.replace("release",""),cmd.replace("release","")) !== undefined        
361    )
362    {
363        if(execute)
364        {            
365            release_key(cmd.replace("release","")); 
366        }
367    }
368    else if(cmd == "await_action_button_released")
369    {
370        if(execute)
371        { 
372            await action_button_released(execution_id);
373        }
374    }
375    else if(cmd == 'toggle_action_buttons')
376    {
377        if(execute)
378        {
379            let this_buttons_label = $('#ck'+execution_id).text();
380            $(".custom_key").not(`":contains('${this_buttons_label}')"`).toggle();
381        }
382    } 
383    else
384    {
385        valid=false;
386    }
387    return valid;
388}
389
390
391
392async function validate_custom_key_form(){
393    var is_valid=true;
394    is_valid &=await validate_custom_key();
395    is_valid &=await validate_action_script();
396    return is_valid;
397}
398
399async function reset_validate(){
400    document.querySelectorAll(".is-invalid").forEach(el=>el.classList.remove("is-invalid"))
401    document.querySelectorAll(".is-valid").forEach(el=>el.classList.remove("is-valid"))
402}
403
404async function validate_custom_key(){
405    var is_valid=true;
406    var input_button_text=$('#input_button_text');
407    var input_button_shortcut=$('#input_button_shortcut');
408
409    input_button_text.removeClass("is-valid").removeClass("is-invalid");
410    input_button_shortcut.removeClass("is-valid").removeClass("is-invalid");
411
412    if( input_button_text.val().trim().length==0 && 
413        input_button_shortcut.val().trim().length==0)
414    {
415        input_button_text.addClass("is-invalid");
416        input_button_shortcut.addClass("is-invalid");
417        is_valid=false;
418    }
419    else
420    {  
421        input_button_text.addClass("is-valid");
422        input_button_shortcut.addClass("is-valid");
423    }
424  
425    return is_valid;
426};
427async function validate_action_script()
428{
429    let is_valid=true;
430    let input_action_script=$('#input_action_script');
431    let script_lang = $('#button_script_language').text();
432    input_action_script.removeClass("is-valid").removeClass("is-invalid");
433    if( (await parse_script(script_lang, input_action_script.val())) == false)
434    {
435        input_action_script.addClass("is-invalid");
436        is_valid=false;
437    }
438    else
439    {
440        input_action_script.addClass("is-valid");
441    }
442    return is_valid;
443};
444
445
446
447function execute_joystick_script(cmd_tokens)
448{
449    var portnr=cmd_tokens[1];
450    var dir= cmd_tokens[2].toUpperCase();
451    var down_or_release = cmd_tokens[3];
452
453    var previous_owner = set_port_owner(portnr,PORT_ACCESSOR.BOT);
454    var native_joy_cmd=null;
455    if(dir == "FIRE")
456    {
457        native_joy_cmd= portnr+(down_or_release == 1 ?"PRESS_"+dir:"RELEASE_"+dir);
458    }
459    else
460    {
461       native_joy_cmd= portnr+(down_or_release == 1 ?"PULL_"+dir:"RELEASE_"+((dir=="LEFT" || dir=="RIGHT")?"X":"Y"));
462    }
463    send_joystick(PORT_ACCESSOR.BOT,portnr,native_joy_cmd);
464
465    set_port_owner(portnr,previous_owner);
466 }
467
468 async function load_last_snapshot()
469 {
470    let app_snaps = await get_snapshots_for_app_title(global_apptitle);  
471    if(app_snaps.length>0)
472    {
473        var snapshot = app_snaps[app_snaps.length-1];
474        if(is_running())
475        {
476            wasm_halt();
477        }
478        wasm_loadfile(
479            snapshot.title+".vc64",
480            snapshot.data, 
481            snapshot.data.length);
482        if(!is_running())
483        {
484            app.button_run_click();
485        }  
486        {
487            wasm_run();
488        } 
489    }
490 }
491
492 function sprite_xpos(sprite_id)
493 {
494   var all_sprite_pos = wasm_sprite_info().split(",");
495   return all_sprite_pos[sprite_id*2];
496 }
497
498 function sprite_ypos(sprite_id)
499 {
500   var all_sprite_pos = wasm_sprite_info().split(",");
501   return all_sprite_pos[sprite_id*2+1];
502 }
503
504
505class BotEvent {
506    log() {
507        console.log(`BotEvent { other_sprite=${this.other_sprite}, x_dir=${this.x_dir}, y_dir=${this.y_dir}, distance=${this.distance}}`)
508    }
509}
510
511class Bot {
512    constructor(proccess_id, sprite_id, min_diagonal_range_angle=2
5126, max_diagonal_range_angle=28) {
513        this.sprite_id = sprite_id;
514        this.process_id = proccess_id;
515        this.min_diagonal_range_angle=min_diagonal_range_angle; 
516        this.max_diagonal_range_angle=max_diagonal_range_angle; 
517        this.process_event=false;
518    }
519    fetch_sprite_info(){
520        this.all_sprite_pos = wasm_sprite_info().split(",");
521    }
522
523
524    get _x() {
525        return this.x(this.sprite_id);
526    }
527    get _y() {
528        return this.y(this.sprite_id);
529    }
530
531    get port() {
532        this.controller_port;
533    }
534
535    next_event(bot_event){
536        console.log(` event=${bot_event.other_sprite} ${bot_event.x_dir} ${bot_event.y_dir} \n`);
537    }
538
539    distance(other_sprite) {
540        const dx = this._x - this.x(other_sprite);
541        const dy = this._y - this.y(other_sprite);
542        return Math.hypot(dx, dy);
543    }
544    
545    x(sprite){ return this.all_sprite_pos[sprite*2]};
546    y(sprite){ return this.all_sprite_pos[sprite*2+1]};
547
548    async aim_and_shoot(bot_event) {
549        var x_aim = bot_event.x_dir;
550        var y_aim = bot_event.y_dir;
551        
552        if(x_aim != null || y_aim != null)
553        {
554          set_port_owner(port, 
555            PORT_ACCESSOR.BOT);
556          await action(`j${port}left0=>j${port}up0`);
557      
558          await action(`j${port}fire1`);
559          if(x_aim != null)
560           await action(`j${port}${x_aim}1`);
561          if(y_aim != null)
562            await action(`j${port}${y_aim}1`);
563          await action("60ms");
564          if(x_aim != null)
565            await action(`j${port}${x_aim}0`);
566          if(y_aim != null)
567            await action(`j${port}${y_aim}0`);
568          await action(`j${port}fire0`);
569          await action("60ms");
570      
571          set_port_owner(
572            port,
573            PORT_ACCESSOR.MANUAL
574          );
575        }
576
577    }
578
579    move(direction, time) { 
580
581    }
582
583    sprite_in_shooting_range(other_sprite)
584    {
585        var y_light=this.y(this.sprite_id);
586        var y_dark=this.y(other_sprite);
587        var x_light=this.x(this.sprite_id);
588        var x_dark=this.x(other_sprite);
589      
590        var y_diff=Math.abs(y_light - y_dark);
591        var x_diff=Math.abs(x_light - x_dark);
592        var angle =  Math.atan2(y_diff, x_diff) * 180 / Math.PI; 
593    
594        var x_aim=null;
595        var y_aim=null;
596        if( y_diff<10 || this.min_diagonal_range_angle<angle && angle<this.max_diagonal_range_angle )
597        {
598            var x_rel = x_light-x_dark;  
599            x_aim=x_rel > 0 ?'left':'right';   
600        }
601        if( x_diff <10 || this.min_diagonal_range_angle<angle && angle<this.max_diagonal_range_angle)
602        {
603            var y_rel = y_light-y_dark;  
604            y_aim=y_rel > 0 ?'up':'down';   
605        }
606        if(x_aim != null || y_aim != null)
607        {
608            var event = new BotEvent();
609            event.other_sprite = other_sprite;
610            event.x_dir = x_aim;
611            event.y_dir = y_aim;
612            event.distance = this.distance(other_sprite);
613            return event; 
614        }
615        else
616        {
617            return null;
618        }
619    }
620
621    async run(controller_port=1) {
622        this.controller_port=controller_port;
623
624        while(not_stopped(this.process_id))
625        {
626          //check for events
627          this.fetch_sprite_info();
628          for(var id = 0; id<8; id++)
629          {
630            if(id != this.sprite_id)
631            {
632                var sprite_event = await this.sprite_in_shooting_range(id);
633                if(sprite_event != null)
634                {
635                    this.next_event(sprite_event);
636                }
637            }
638          }
639          await action("20ms"); // time of one frame 1000/50=20ms
640        }
641    }
642}
643
644
645class ArchonBot extends Bot {
646    async next_event(event) {
647        if( event.other_sprite == 1 )
648        {
649            this.aim_and_shoot(event);
650        }
651    }
652}
653
654class FalconBot extends Bot {
655    constructor(proccess_id, sprite_id) {
656        super(proccess_id, sprite_id);
657        this.min_diagonal_range_angle = 30;
658        this.max_diagonal_range_angle = 35;
659    }
660
661    async next_event(event) {
662        if( 0<event.other_sprite  )
663        {
664            event.log();
665
666            if(this._y > 100)
667            {//prevent crash on ground
668                await action("j1up1=>300ms=>j1up0");
669            }
670            else if(event.y_dir == 'up' )
671            {
672            }
673            else if(event.x_dir == 'left' //|| event.x_dir == 'right'  
674               && event.distance < 150 /*&& this.process_event == false */
675            )
676            {
677                this.process_event = true; 
678                //await this.aim_and_shoot(event);
679                if(event.y_dir == 'down')
680                {
681                    await action("j1down1");
682                }
683    
684                await action("j1fire1=>100ms=>j1fire0")
685
686                if(event.y_dir == 'down')
687                {
688                    await action("j1down0=>10ms=>j1up1=>60ms=>j1up0");
689                }
690                console.log("shoot");
691              
692                this.process_event = false;
693            }
694            else
695            {
696                console.log("no shoot");
697            }
698        }
699    }
700}
701
702function wasm_runstop_restore()
703{
704    wasm_schedule_key(7, 7, 1, 0); //press runstop 
705    wasm_schedule_key(9, 9, 1, 2); //press restore 
706    wasm_schedule_key(9, 9, 0, 4); //release restore
707    wasm_schedule_key(7, 7, 0, 6); //release runstop
708}
709
710
711function press_key(key)
712{
713    let c64code=translateKey2(key, key);
714    if(c64code !== undefined )
715    {
716        wasm_schedule_key(c64code.raw_key[0], c64code.raw_key[1], 1, 0);
717        if(c64code.modifier != null)
718        {
719            wasm_schedule_key(c64code.modifier[0], c64code.modifier[1], 1, 0);
720        }
721    }
722}
723function release_key(key)
724{
725    let c64code=translateKey2(key, key);
726    if(c64code !== undefined )
727    {
728        wasm_schedule_key(c64code.raw_key[0], c64code.raw_key[1], 0, 0);
729        if(c64code.modifier != null)
730        {
731            wasm_schedule_key(c64code.modifier[0], c64code.modifier[1], 0, 0);
732        }
733    }
734}
735
736async function wasm_ready_after_reset()
737{
738    let mega_roms=JSON.parse(wasm_rom_info()).kernal.startsWith("mega");
739    //mega roms is ready after 500000 cycles, original C= Rom is ready after 2700000 cycles
740    while(wasm_get_cpu_cycles()<(mega_roms?500000:2700000)) 
741    {
742        await sleep(50);
743    }    
744}
745
746async function action_button_released(id)
747{
748    while(!get_running_script(id).action_button_released) 
749    {
750        await sleep(50);
751    }    
752}
753
754//the following line is only there for codemirrors jshint autocomplete feature
755//this_id and still_active will be overscoped by their equal named pendants in the action blocks 
756var this_id=0; var still_active=function(){alert("NEVER CALLED!!")}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.