[ESA]永恒的星际联盟

 找回密码
 立即注册

!connect_header_login!

!connect_header_login_tip!

查看: 304|回复: 2

[EUD教程] 在星际1里实现F2A功能

[复制链接]
发表于 2021-7-17 13:27:37 | 显示全部楼层 |阅读模式
本帖最后由 Ar3sgice 于 2021-7-17 15:23 编辑

星际2有个著名的功能,F2A,可以同时操作全图的单位。能不能把这个功能移植到星际1里呢?

很多地图如FCW、EVOLVES都有群体攻击的机制,但是都是用小人操作,而且攻击的目标是有限多的点位
这样就离星际2里F2A操作的流畅度差很远

而如果用EUD来做,可以通过MSQC真正实现F2A的效果

MSQC是检测按键的EUD Editor插件,可以检测玩家按了某个键
具体相关的资料可以参考 https://www.scrpg.info/forum.php?mod=viewthread&tid=9848

不过首先,用MSQC直接检测F2的话,是有一个问题的
由于星际1里F2绑定的是切换视角的功能,MSQC根本就检测不到F2这个键!!!

这里就需要一个十分具有创造力的解决方案了:
“先按F,再按2,然后按A”

具体的做法就是:
1. 用MSQC检测玩家按下F键,然后把编队2的单位暂时设定为地图上的一个鸟
2. 玩家按下2的时候就会选中这个鸟
3. 如果鸟接受到了命令(如移动,攻击),把命令广播给地图上所有的单位

================
第一步:检测玩家按下F键
================

用MSQC检测按键十分简单,只需要一个if语句:
  1. if( MemoryEPD(EPD(msqcvar.VKeyPress_F) + player, Exactly, 1) ) {
  2.   // Key F is pressed
  3. }
复制代码
注意此处的VKeyPress_F变量,这个需要在插件里设置
  1. [MSQC]
  2. NotTyping ; KeyPress(F) : VKeyPress_F, 1
复制代码
不过如果你的EUD Editor 3不是代码模式的话它会自动帮你设置好,就不用管这个了

======================
第二步:设置编队2为检测单位(鸟)
======================

首先需要把鸟提前放在地图里,如图
另外可以看到贴了六个地点OrderP1 ... OrderP6,这些等下也会用到

figure1.jpg

演示地图里是因为玩家自选种族,鸟是放给P9然后再Give Unit到玩家的
如果是其他类型的地图,直接放给玩家就可以了

放好鸟之后可以按回车看到鸟的编号

figure2.jpg

单位编号和EUD地址的关系:
  1. If MapIndex == 0: CUnitAddr = 0x59CCA8
  2. If MapIndex != 0: CUnitAddr = 0x59CCA8 + (1700 - MapIndex) * 336
复制代码
所以这6个鸟的内存地址分别为:
  1. const F2Units = [0x59CCA8, 0x628298, 0x628148, 0x627FF8, 0x627EA8, 0x627D58];
复制代码
注意单位编号一定要放在所有玩家单位的前面
因为开图的时候不一定每个位置都有人,如果缺少玩家,后面的单位编号就会错位了

放好鸟之后就是在EUD Editor 3中设置热键
星际里每个玩家的热键组储存在0x57FE60这个地址,每个热键 (0-9) 占48字节,其中每个单位4字节,12个单位
每个玩家的总长度为864(而不是48*10=480,可能是当时星际设计了别的热键但是取消了)
也就是说
  1. 热键单位地址 = 0x57FE60 + 864 * player + 48 * key + 4 * unit
复制代码
在这里,对应每个单位的数值不是CUnit指针,而是一个特殊的数值
  1. 对于地图里预先放置的单位:
  2. If MapIndex == 0: X = 2049
  3. If MapIndex != 0: X = 2049 + (1700 - MapIndex)
  4. 对于游戏过程中创建的单位:
  5. X = UnitIndex + 1 + 2048 * CUnit -> uniquenessIdentifier (0xA5)
  6. (CUnitAddr = 0x59CCA8 + 336 * UnitIndex)
复制代码
也就是说我们的6个鸟对应的数值分别为
  1. const F2UnitUQIs = [2049, 3748, 3747, 3746, 3745, 3744];
复制代码
知道这些就可以写代码了,先写个函数
  1. const tempHotkeyDb = Db("111122223333444455556666777788889999AAAABBBBCCCC111122223333444455556666777788889999AAAABBBBCCCC111122223333444455556666777788889999AAAABBBBCCCC111122223333444455556666777788889999AAAABBBBCCCC111122223333444455556666777788889999AAAABBBBCCCC111122223333444455556666777788889999AAAABBBBCCCC");
  2. // 长度为48*6的数组,用来暂存每个玩家的2号热键

  3. function setHotkeySet2(player) {
  4.         if(Deaths(player, Exactly, 0, 204)) {
  5.                 memcpy(tempHotkeyDb + 48 * player, 0x57FE60 + 96 + 864 * player, 48); // 把当前2号热键复制到暂存的数组tempHotkeyDb
  6.         }
  7.         SetMemory(0x57FE60 + 96 + 864 * player + 0, SetTo, F2UnitUQIs[player]); // 设置快捷键为鸟,其余为空
  8.         SetMemory(0x57FE60 + 96 + 864 * player + 4, SetTo, 0);
  9.         SetMemory(0x57FE60 + 96 + 864 * player + 8, SetTo, 0);
  10.         SetMemory(0x57FE60 + 96 + 864 * player + 12, SetTo, 0);
  11.         SetMemory(0x57FE60 + 96 + 864 * player + 16, SetTo, 0);
  12.         SetMemory(0x57FE60 + 96 + 864 * player + 20, SetTo, 0);
  13.         SetMemory(0x57FE60 + 96 + 864 * player + 24, SetTo, 0);
  14.         SetMemory(0x57FE60 + 96 + 864 * player + 28, SetTo, 0);
  15.         SetMemory(0x57FE60 + 96 + 864 * player + 32, SetTo, 0);
  16.         SetMemory(0x57FE60 + 96 + 864 * player + 36, SetTo, 0);
  17.         SetMemory(0x57FE60 + 96 + 864 * player + 40, SetTo, 0);
  18.         SetMemory(0x57FE60 + 96 + 864 * player + 44, SetTo, 0);
  19. }

  20. function restoreHotkeySet2(player) {
  21.         memcpy(0x57FE60 + 96 + 864 * player, tempHotkeyDb + 48 * player, 48); // 还原快捷键
  22. }
复制代码
在前面说的检测F键的地方:
  1. if(MemoryEPD(EPD(msqcvar.VKeyPress_F) + player, Exactly, 1)) {
  2.         setHotkeySet2(player);
  3.         SetDeaths(player, SetTo, 24, 204); // 设置延时
  4. }
  5.         
  6. if(Deaths(player, Exactly, 1, 204)) {
  7.         restoreHotkeySet2(player); // 延时到期后还原热键
  8. }
  9. SetDeaths(player, Subtract, 1, 204);
复制代码
这样就能实现在玩家按下F键之后,暂时的把2号热键设置为鸟的功能。

=========================
第三步:把鸟受到的命令广播给全图单位
=========================

首先,在地图一开始(onPluginStart)把鸟的速度改为0,并且命令改为187(Junk Yard Dog)
这样当命令不是187的时候,我们就知道鸟被控制了
  1. function onPluginStart() {
  2.         var i;
  3.         for(i=0; i<6; i++) {
  4.                 // 设置命令,包括命令状态
  5.                 SetMemoryXEPD(F2UnitEPDs[i] + 76/4, SetTo, 187 * 256 + 0 * 65536, 0x00FFFF00);
  6.                 // 设置最高速度和加速度为0,防止鸟乱飞
  7.                 SetMemoryEPD(F2UnitEPDs[i] + 52/4, SetTo, 0);
  8.                 SetMemoryEPD(F2UnitEPDs[i] + 56/4, SetTo, 0);
  9.         }
  10. }
复制代码
检测鸟是否被选中,然后检测鸟接受到的命令:
  1. selectedUnit = dwread_epd(selectionEPDs[player]); // 检测每个玩家选择的单位是否为鸟,其实不检测也可以,但是这样可以避免潜在的BUG
  2. if(selectedUnit > 0) {
  3.         if(selectedUnit == F2Units[player]) { // 如果鸟被选中
  4.                 bcastOrder = bread(selectedUnit + 77); // 读取当前鸟的命令(CUnit+77)
  5.                 if(bcastOrder != 187) {
  6.                         bcastX = wread(selectedUnit + 88); // 获取命令坐标,如移动、攻击、巡逻的目标
  7.                         bcastY = wread(selectedUnit + 90);
  8.                         setloc(locStart + player, bcastX, bcastY); // 把玩家对应的地点OrderPx位置设置为鸟的目标位置
  9.                         if(bcastOrder == 6 || bcastOrder == 49) { // 6是移动,49是跟随(移动到另一个单位)
  10.                                 SetDeaths(player, SetTo, 1, 94); // 设置死亡数,然后用普通的触发移动过去
  11.                         }
  12.                         else if(bcastOrder == 10 || bcastOrder == 14) { // 10是攻击单位,14是攻击地板
  13.                                 SetDeaths(player, SetTo, 2, 94); // 设置死亡数,然后用普通的触发A过去
  14.                         }
  15.                         else if(bcastOrder == 152) { // 巡逻
  16.                                 SetDeaths(player, SetTo, 3, 94); // 设置死亡数,然后用普通的触发巡逻过去
  17.                         }
  18.                         else if(bcastOrder == 2 || bcastOrder == 3 || bcastOrder == 107){ // 2和3是停止,107是hold
  19.                                 broadcastStaticOrder(player, bcastOrder, 0); // 停止和hold不能用普通触发,只能用EUD操作
  20.                         }
  21.                         SetMemoryX(selectedUnit + 76, SetTo, 187 * 256 + 0 * 65536, 0x00FFFF00); // 把鸟的命令 (CUnit+77) 重置,包括命令状态 (CUnit+78)
  22.                 }
  23.         }
  24. }
复制代码





 楼主| 发表于 2021-7-17 13:29:10 | 显示全部楼层
本帖最后由 Ar3sgice 于 2021-7-17 15:07 编辑

其中的broadcastStaticOrder函数:
  1. const allowedUnitTypes = [0,1,2,3,5,8,9,12,32,34,37,38,39,40,43,44,45,46,47,58,60,61,62,63,65,66,67,68,70,71]; // 允许F2操控的单位类型
  2. const allowedUnitTypeCount = 30;// 实际上有32个,因为金甲和航母是特殊处理的

  3. function broadcastStaticOrder(player, bcastOrder, bcastOrderState) {
  4.         var currentOrder, currentUnitType, ut, unitOrder;
  5.         foreach(unit, epd : EUDLoopPlayerUnit(player)) { // 循环当前玩家的每个单位
  6.                 currentOrder = bread_epd(epd + 76/4, 1); // 检测每个单位当前的命令
  7.                 if(currentOrder != bcastOrder) {
  8.                         currentUnitType = wread_epd(epd + 100/4, 0);
  9.                         for(ut=0; ut<allowedUnitTypeCount; ut++) {
  10.                                 if(currentUnitType == allowedUnitTypes[ut]) { // 只允许特定类别的单位被F2操作(总不能让农民也别采矿吧)
  11.                                         unitOrder = bread_epd(epd + 76/4, 1);
  12.                                         if(unitOrder == 2 || unitOrder == 3 || unitOrder == 6 || unitOrder ==10 || unitOrder == 14 || unitOrder == 49 || unitOrder == 107 || unitOrder == 152) {
  13.                                                 // 只控制当前命令为停止/移动/攻击/巡逻/hold的单位。
  14.                                                 // 因为直接改所有的命令,有时候是会出问题的,比如z的单位出生时有个出生命令,直接改成别的,就会导致生不出来而卡住,等等
  15.                                                 if(bcastOrder == 2 || bcastOrder == 3) {
  16.                                                         // 停下
  17.                                                         SetMemoryXEPD(epd + 76/4, SetTo, 1 * 256 + 0 * 65536, 0x00FFFF00);
  18.                                                 }
  19.                                                 else {
  20.                                                         // hold
  21.                                                         SetMemoryXEPD(epd + 76/4, SetTo, 107 * 256 + 0 * 65536, 0x00FFFF00);
  22.                                                 }
  23.                                         }
  24.                                         break;
  25.                                 }
  26.                         }
  27.                         if(currentUnitType == 83) { // 需要注意的是航母和金甲的命令特殊,必须单独判断
  28.                                 unitOrder = bread_epd(epd + 76/4, 1);
  29.                                 if(unitOrder > 0) {
  30.                                         if(bcastOrder == 2 || bcastOrder == 3) {
  31.                                                 SetMemoryXEPD(epd + 76/4, SetTo, 7 * 256 + 0 * 65536, 0x00FFFF00); // Order #7 Stop (Reaver)
  32.                                         }
  33.                                         else {
  34.                                                 SetMemoryXEPD(epd + 76/4, SetTo, 62 * 256 + 0 * 65536, 0x00FFFF00); // Order #62 Hold Position (Reaver)
  35.                                         }
  36.                                 }
  37.                         }
  38.                         if(currentUnitType == 72) { // 上面是金甲,这个是航母
  39.                                 unitOrder = bread_epd(epd + 76/4, 1);
  40.                                 if(unitOrder > 0) {
  41.                                         if(bcastOrder == 2 || bcastOrder == 3) {
  42.                                                 SetMemoryXEPD(epd + 76/4, SetTo, 52 * 256 + 0 * 65536, 0x00FFFF00); // Order #52 Stop (Carrier)
  43.                                         }
  44.                                         else {
  45.                                                 SetMemoryXEPD(epd + 76/4, SetTo, 57 * 256 + 0 * 65536, 0x00FFFF00); // Order #57 Hold Position (Carrier)
  46.                                         }
  47.                                 }
  48.                         }
  49.                 }
  50.         }
  51. }
复制代码
以及地图里的普通触发,用来实现移动/攻击/巡逻:
  1. Trigger("Player 1"){ // 以P1为例,每个玩家都要有一组
  2. Conditions:
  3.         Deaths("Current Player", "Kakaru (Twilight)", Exactly, 1);

  4. Actions:
  5.         Set Deaths("Current Player", "Kakaru (Twilight)", Set To, 0);
  6.         Order("Current Player", "Terran Battlecruiser", "Anywhere", "OrderP1", move);
  7.         Order("Current Player", "Terran Firebat", "Anywhere", "OrderP1", move);
  8.         Order("Current Player", "Terran Ghost", "Anywhere", "OrderP1", move);
  9.         ... // 给每种单位各自下命令
  10.         Order("Current Player", "Zerg Zergling", "Anywhere", "OrderP1", move);
  11.         Preserve Trigger();
  12. }

  13. //-----------------------------------------------------------------//

  14. Trigger("Player 1"){
  15. Conditions:
  16.         Deaths("Current Player", "Kakaru (Twilight)", Exactly, 2);

  17. Actions:
  18.         Set Deaths("Current Player", "Kakaru (Twilight)", Set To, 0);
  19.         Order("Current Player", "Terran Battlecruiser", "Anywhere", "OrderP1", attack);
  20.         Order("Current Player", "Terran Firebat", "Anywhere", "OrderP1", attack);
  21.         Order("Current Player", "Terran Ghost", "Anywhere", "OrderP1", attack);
  22.         ... // 给每种单位各自下命令
  23.         Order("Current Player", "Zerg Zergling", "Anywhere", "OrderP1", attack);
  24.         Preserve Trigger();
  25. }

  26. //-----------------------------------------------------------------//

  27. Trigger("Player 1"){
  28. Conditions:
  29.         Deaths("Current Player", "Kakaru (Twilight)", Exactly, 3);

  30. Actions:
  31.         Set Deaths("Current Player", "Kakaru (Twilight)", Set To, 0);
  32.         Order("Current Player", "Terran Battlecruiser", "Anywhere", "OrderP1", patrol);
  33.         Order("Current Player", "Terran Firebat", "Anywhere", "OrderP1", patrol);
  34.         ... // 给每种单位各自下命令
  35.         Order("Current Player", "Zerg Zergling", "Anywhere", "OrderP1", patrol);
  36.         Preserve Trigger();
  37. }
复制代码

以上的代码编译后,效果图:


figure3.jpg

按F+2操作鸟就可以操控全图单位了!!!

小细节:

1. 检测单位为什么要用鸟
(1) 检测单位必须是飞行单位,因为如果让地面单位走到水里,它是不会过去的(会变成目标为自身坐标);
(2) 跟其他小动物单位一样,鸟不能和别的单位同时选中,避免可能会出现的BUG;
(3) 鸟的移动方式设置是用Flingy.dat控制,这样只要简单的把最高速度设置为0就能防止鸟乱飞
其实本来是想用overmind的,可惜建筑不能接受命令……

2. 关于地图里提前放置单位
这里是为了简化计算量,就把检测单位提前放在地图上了,这样单位的内存地址是固定的,计算方便
用触发放置也是可以的,实现方法是“先把变量设置成即将创建的单位,再使用CreateUnit触发”
比如
  1. F2Units[0], F2UnitEPDs[0] = dwepdread_epd(EPD(0x628438)); // "SetNextCreateUnitPtrEpd"
  2. CreateUnit(1, 94, "BirdP1", P1);
复制代码

演示地图:

6V2 F2A EUD3.scx

366.73 KB, 下载次数: 1

源地图和代码.rar

106.97 KB, 下载次数: 1

回复 支持 反对

使用道具 举报

发表于 2021-8-18 16:30:52 | 显示全部楼层
太厉害了!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|站点地图|永恒的星际联盟 ( 苏ICP备17020173号-1 )

GMT+8, 2021-12-4 18:21 , Processed in 0.014719 second(s), 7 queries , Gzip On, File On.

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表