太阳神三国杀自制武将武将工厂怎么导入武将呀,自己...

教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)96_太阳神三国杀diy-牛宝宝文章网
教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)96 太阳神三国杀diy
在cpp区发了那篇DIY教程贴之后,我转战到了lua区,发现大家都是再写lua武将的DIY,没有人关心AI的lua。在我看来,与cpp相比,lua来实现DIY的武将优点是***卸载方便且不会影响cpp源代码的整洁,缺点是不能debug,通过设置断点来观察变量,在测试的过程中比较辛苦,还有就是偏于与系统耦合的技能比较难实现。这两天把AI部分的lua代码看了一遍清楚了,除了身份判断部分,基本清楚了它的原理。鉴于lua武将DIY的教程和范例已经比较完善,这次我就把AI的编写简要的整理一下。附件中的内容是和这次教程相关的代码、相关武将的卡牌图片以及本帖的PDF版本。附件中有“侠”三武将的lua和AI,以及两个我DIY的娱乐武将的AI(娱乐武将用CPP实现的,由于有一个修改性别的技能改变了底层的Player类,因此无法用lua完美实现,所以就不提供代码了,有兴趣的同学可以和我交流)。首先,AI的编写与lua技能的代码有很大关系,我用自己设计的两个武将和“侠”三武将做了实验并且完成了这五个武将的AI。在编写“侠”三武将的AI时遇到了一些问题,后来我把寒秋的代码优化了一下才解决了问题。附件里会有“侠”三武将的代码,一个是原版的,一个是我改进过的,大家可以对比下。(并没有冒犯的意思,纯学术上的交流)所以算是给广大lua的DIY爱好者们几条编程方面的建议吧。1. 写代码要整齐,缩进量要层层递进,保证代码的易读性2. 写触发技的时候,要清楚每个触发事件的原理,最好不要加入多余的触发事件,否则可能引入异常的行为。当然,一般来说引入多余事件只是降低程序运行的效率罢了,对于神杀影响应该不大。3. 没有必要的时候不要重载can_trigger、enabled_at_play、enabled_at_response这些函数。对于can_trigger这个函数,重载直接return true然后在on_trigger里判断玩家是否拥有该触发技和使用默认的can_trigger基本是一样的(因为can_trigger本身返回值就是触发者是否拥有该技能且触发者是否活着,貌似死人没法触发技能)。4. 如果一个触发技可以在多个时间下触发,在写on_trigger函数的时候大家请按照触发的事件来写条件分支语句,这样逻辑会更加清晰一些。5. 尽量避免在视作技中使用askForCard这个函数,我来举个例子:如果一个视作技可以把闪当作无中生有来使用,如果你不用askForCard,那么操作流程将是点击技能按钮à选择一张闪à点击确定当无中生有打出;但是如果使用了askForCard并且你装备了八卦阵,那么你点击技能按钮à系统提示你需要打出一张闪à使用八卦阵à判红à直接打出一张无中生有。一张牌当无中生有和零张牌当无中生有,知道区别了吧。6. 在触发技一定要使用askForSkillInvoke(player,self:objectName(),data),这里要注意第三个参数data,它包含触发事件的详细信息,一般卖血技的是damage的结构体、无言那种是cardEffect结构体、烈弓是slashEffect结构体。这些data中的信息在写AI的时候十分重要,因此务必要加上以便于AI中完成复杂的判断。我之前写AI就是因为askForSkillInvoke(player,self:objectName())导致需要的变量总是空值,郁闷了很久。7. 触发技中askForCard之前最好加上askForSkillInvoke。对于人来说,如果不加askForSkillInvoke,不想发动机能的时候直接点取消就行,加上了askForSkillInvoke无非就是在提示是否使用XXX技能时多点一个确定罢了。但是对于AI的编写,如果不加askForSkillInvoke,那么这个技能对应的AI就要写在smart-ai.lua的SmartAI:askForCard里,有点像cpp中的系统耦合技,这对于代码的整洁还有AI的移植是不利的。但是加上askForSkillInvoke后,我们就可以单独的对这个选择写一个AI,消除了耦合。OK,建议提完了,开始说AI的编写吧。首先,基本上所有的非锁定技的视作技和触发技都可以独立新建一个lua文件编写,禁止技在smart-ai中的cardProhibit和slashProhibit里编写,锁定技在smart-ai.lua中的damageIsEffective、slashIsAvailable、prohibitUseDirectly、isCompulsoryView中编写,一些锁定技的响应(需要调用askForCard的)在smart-ai中的askForCard里编写,卡牌转换类技能在smart-ai中的getSkillViewCard里编写。我把AI技能的编写分为几类:1. 将卡牌视为三国杀已有主动使用卡牌的视作技,比如双雄、连环、火计2. 将卡牌视为三国杀已有卡牌的视作技,这些卡牌用于响应,比如倾国、看破、急救注:武圣、龙胆、酒池这些技能既属于第一类也属于第二类3. 将卡牌视为技能卡的视作技,比如青囊、离间、驱虎4. 所有的非锁定技的触发技,比如刚烈、八阵、悲歌5. 禁止技,比如空城、谦逊、帷幕6. 使效果无效的技能,暂且称无效技,比如无言、智迟、神君;有威慑力的技能,比如刚烈、恩怨、雷击7. 视作锁定技,比如戒酒、武神8. 对出杀次数、杀指定目标数、杀的距离修改的技能,比如天义、咆哮、方天画戟9. 响应时的斟酌,比如AOE奸雄收牌、无双肉林只有一张闪就直接掉血此外,一些技能还涉及到选项,比如志继觉醒摸牌还是回血、反间选择花色、无谋弃掉标记还是流失体力;另一些技能涉及到目标的选择,比如驱虎赢了选择受伤害者、旋风选择伤害者或杀的目标、眩惑来的牌给谁。这就要求我们具体问题具体分析了。第一类技能编写的通式是:local skillname_skill={}skillname _skill.name=& skillname &table.insert(sgs.ai_skills, skillname)skillname _skill.getTurnUseCard=function(self)判断是否满足应该使用的条件,若不满足则return nillocal card选则要发动技能的卡牌,赋值给cardif not card then return nil endlocal suit = card:getSuitString()local number = card:getNumberString()local card_id = card:getEffectiveId()local card_str = (&转换卡牌名称:skillname[%s:%s]=%d&):format(suit, number, card_id)
local skillcard = sgs.Card_Parse(card_str)assert(skillcard)return skillcardend例子是以下这个武将的技能—求败:[&dgqb&] = &独孤求败&,[&luawujian&] = &无剑&,[&luayoujian&] = &有剑&,[&luayoujiandev&] = &有剑&,[&luaqiubai&] = &求败&,[&luaqiubaix&] = &求败&,[&:luawujian&] = &当你没有装备武器时你的杀不可被闪避。&,[&:luaqiubai&] = &回合内你可以将任意武器牌当决斗使用。若决斗成功则你可以摸两张牌否则对方摸两张牌&,[&:luayoujian&] = &当你装备有武器时本回合你可以出X张杀,X为你当前的攻击范围,回合内你使用的所有杀都将被计数(不包括决斗出的杀)。&,--luaqiubailocal luaqiubai_skill={}--设置技能名称luaqiubai_skill.name=&luaqiubai&--把这个技能添加进ai_skills列表中,使AI可以使用这个技能table.insert(sgs.ai_skills,luaqiubai_skill)--判断技能是否可以发动的函数luaqiubai_skill.getTurnUseCard=function(self)--获取手牌中杀的个数local slash_num = self:getCardsNum(&Slash&)--获取该回合出杀的次数local slash_used = self.player:usedTimes(&Slash&)--如果已装备武器,且手中有杀多于一个,--且获取该回合出杀的次数小于攻击范围,则不发动该技能--这是为了保证有剑和求败两个技能利用最大化if self.player:getWeapon() and slash_num & 0and slash_used & self.player:getAttackRange() then return nil end--获取我所有的手牌和装备牌local cards = self.player:getCards(&he&)cards=sgs.QList2Table(cards)local card--根据使用价值来排序self:sortByUseValue(cards,true)--遍历所有卡牌for _,acard in ipairs(cards)
do--如果是武器牌则使用这个卡牌if acard:inherits(&Weapon&) thencard = acardbreakendend--如果没选发动技能的卡牌则返回空值if not card then return nil endlocal suit = card:getSuitString()local number = card:getNumberString()local card_id = card:getEffectiveId()--构造卡牌字符串,卡牌名称:技能名称[花色:点数]=卡牌IDlocal card_str = (&duel:luaqiubai[%s:%s]=%d&):format(suit, number, card_id)--指定要使用的卡牌local skillcard = sgs.Card_Parse(card_str)assert(skillcard)return skillcardend第二类技能编写是在smart-ai.lua中的getSkillViewCard中添加代码,用于响应的卡牌有杀、闪、桃、酒、无懈,我们只需要在对应的分支语句里添加即可。比如在对闪的处理可以看到龙魂、龙胆和倾国,如果我们加一个技能叫“我闪”,这个技能可以把装备牌当闪使用,那么用下方红色的代码就能实现它的AI:…………elseif class_name == &Jink& thenif player:hasSkill(&longhun&) and player:getHp() &= 1 thenif card:getSuit() == sgs.Card_Club thenreturn (&jink:longhun[%s:%s]=%d&):format(suit, number, card_id)endendif card_place ~= sgs.Player_Equip thenif player:hasSkill(&longdan&) and card:inherits(&Slash&) thenreturn (&jink:longdan[%s:%s]=%d&):format(suit, number, card_id)elseif player:hasSkill(&qingguo&) and card:isBlack() thenreturn (&jink:qingguo[%s:%s]=%d&):format(suit, number, card_id)endendif card_place == sgs.Player_Equip and player:hasSkill(&woshan&) thenreturn (&jink:woshan[%s:%s]=%d&):format(suit, number, card_id)end…………有一个情况比较特殊,zeroCardView就是把0张牌当某牌,这个很特殊,目前只用来实现酒诗的AI,前两种技能如果是把0张牌当某牌,AI都可以用这个函数实现。如果写一个技能“万兽”和酒诗相似,只是正面向上时翻面可以当南蛮入侵,那么添加红色代码即可:local function zeroCardView(class_name, player)if class_name == &Analeptic& thenif player:hasSkill(&jiushi&) and player:faceUp() thenreturn (&analeptic:jiushi[no_suit:0]=.&)endelse if class_name == &SavageAssault& thenif player:hasSkill(&wanshou&) and player:faceUp() thenreturn (&savage_assault:wanshou[no_suit:0]=.&)endendend第三类技能编写的通式是:local skillname_skill={}skillname _skill.name=& skillname &table.insert(sgs.ai_skills, skillname)skillname _skill.getTurnUseCard=function(self)判断是否满足应该使用的条件,若不满足则return nillocal card选则要发动技能的卡牌,赋值给cardif not card then return nil endlocal card_str = (&@SkillNameCard=%d&):format(card:getId())local skillcard = sgs.Card_Parse(card_str)assert(skillcard)return skillcardendsgs.ai_skill_use_func[&SkillNameCard&]=function(card,use,self)local target选择技能发动的目标,若没找到合适目标则return niluse.card = carduse.to =append(target)returnend例子是以下这个武将的技能—争宠:[&wujian&] = &吴健&,[&zhengchong&] = &争宠&,[&:zhengchong&] = &出牌阶段,你可以弃一张手牌,指定两名角色。若如此做,视为其中一名女性角色对另一名女性角色使用一张【决斗】(此【决斗】不可被【无懈可击】响应),每回合限用一次&,[&yange&] = &阉割&,[&:yange&] = &你每次对其他男性角色造成伤害,可使收到该伤害的角色变为女性,直到游戏结束&,--zhengchonglocal zhengchong_skill={}--设置技能名称zhengchong_skill.name=&zhengchong&--把这个技能添加进ai_skills列表中,使AI可以使用这个技能table.insert(sgs.ai_skills,zhengchong_skill)--判断技能是否可以发动的函数zhengchong_skill.getTurnUseCard=function(self)--如果该回合发动过则不发动if self.player:hasUsed(&ZhengchongCard&) thenreturn教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)96_太阳神三国杀diyend --如果我有手牌或装备 if not self.player:isNude() then
local card
local card_id
--如果我装备了白银狮子并且受伤了,那就用白银狮子发动技能
if self:isEquip(&SilverLion&) and self.player:isWounded() then
card = sgs.Card_Parse(&@ZhengchongCard=& .. self.player:getArmor():getId())
--如果我没受伤或没装备狮子,且手牌数大于体力值 elseif self.player:getHandcardNum() & self.player:getHp() then
--获取我所有手牌
local cards = self.player:getHandcards()
cards=sgs.QList2Table(cards)
--对所有手牌进行遍历
for _, acard in ipairs(cards) do--如果是基本牌或装备牌或五谷丰登且不是桃和屎if (acard:inherits(&BasicCard&) or acard:inherits(&EquipCard&) or acard:inherits(&AmazingGrace&))and not acard:inherits(&Peach&) and not acard:inherits(&Shit&) then
--获取这张牌的IDcard_id = acard:getEffectiveId()breakendend--如果我没受伤或没装备狮子,且手牌数不大于体力值,且有装备elseif not self.player:getEquips():isEmpty() thenlocal player=self.player--按照有武器、有进攻马、有防御马、有防具且手牌数不大于1的顺序弃置来发动技能if player:getWeapon() then card_id=player:getWeapon():getId()elseif player:getOffensiveHorse() then card_id=player:getOffensiveHorse():getId()elseif player:getDefensiveHorse() then card_id=player:getDefensiveHorse():getId()elseif player:getArmor() and player:getHandcardNum()&=1 then card_id=player:getArmor():getId()endend--如果我没受伤或没装备狮子,且手牌数不大于体力值,且装备只有防具,且手牌数大于1if not card_id thencards=sgs.QList2Table(self.player:getHandcards())--对所有手牌进行遍历for _, acard in ipairs(cards) do--如果是基本牌或装备牌或五谷丰登且不是桃和屎if (acard:inherits(&BasicCard&) or acard:inherits(&EquipCard&) or acard:inherits(&AmazingGrace&))and not acard:inherits(&Peach&) and not acard:inherits(&Shit&) then
card_id = acard:getEffectiveId()breakendendend--如果还没有选到发动技能的卡牌就不发动了否则返回技能卡牌字符串
if not card_id thenreturn nilelsecard = sgs.Card_Parse(&@ZhengchongCard=& .. card_id)return cardendendreturn nilend--判断技能卡牌该如何使用的函数sgs.ai_skill_use_func[&ZhengchongCard&]=function(card,use,self)--一个技能内部使用的函数,用来返回杀最多的友方女性local findFriend_maxSlash=function(self,first)self:log(&Looking for the friend!&)local maxSlash = 0local friend_maxSlash--遍历每个友方玩家,找到杀最多的那个女性for _, friend in ipairs(self.friends_noself) doif (self:getCardsNum(&Slash&, friend)& maxSlash) and friend:getGeneral():isFemale() thenmaxSlash=self:getCardsNum(&Slash&, friend)friend_maxSlash = friendendend--如果找到了杀最多的友方女性if friend_maxSlash then--定义一个用来判断是否安全的布尔变量local safe = false--被决斗的人要是有刚烈、反馈、恩怨if (first:hasSkill(&ganglie&) or first:hasSkill(&fankui&) or first:hasSkill(&enyuan&)) then--如果他们1血0手牌则判断为安全if (first:getHp()&=1 and first:getHandcardNum()==0) then safe=true end--若敌方没有刚烈、反馈、恩怨,且友方杀的数量大于敌方手牌数则判断为安全elseif (self:getCardsNum(&Slash&, friend_maxSlash) &= first:getHandcardNum()) then safe=true end--如果安全则返回杀最多的友方女性否则返回空值if safe then return friend_maxSlash endelseself:log(&unfound&)endreturn nilend--如果该回合没发动过该技能if not self.player:hasUsed(&ZhengchongCard&) then--将敌人按照体力值排序self:sort(self.enemies, &hp&)local females = {}local first, secondlocal zhugeliang_kongcheng --改变两专门用于识别空城诸葛亮--创建一个虚拟的决斗卡牌local duel = sgs.Sanguosha:cloneCard(&duel&, sgs.Card_NoSuit, 0)--遍历每个敌人for _, enemy in ipairs(self.enemies) do--如果敌人中有空城诸葛亮且已选被决斗者且空城诸葛亮决斗伤害对被决斗者有效if zhugeliang_kongcheng and #females==1 and self:damageIsEffective(zhugeliang_kongcheng, sgs.DamageStruct_Normal, females[1])--则发起决斗者为空城诸葛亮then table.insert(females, zhugeliang_kongcheng) end--如果该敌人是女性且没有无言if enemy:getGeneral():isFemale() and not enemy:hasSkill(&wuyan&) then
--如果该敌人有空城且没有手牌,则该敌人是空城诸葛亮if enemy:hasSkill(&kongcheng&) and enemy:isKongcheng() then zhugeliang_kongcheng=enemy--如果该敌人不是诸葛亮else者if #females == 0 and self:hasTrickEffective(duel, enemy) then table.insert(females, enemy)--如果发起决斗者还没定且决斗伤害对被决斗者有效,则该敌人为发起决斗者elseif #females == 1 and self:damageIsEffective(enemy, sgs.DamageStruct_Normal, females[1]) then table.insert(females, enemy) endend
--如果被决斗者还没定且决斗对该敌人有效,则该敌人为被决斗--如果决斗的两个人选好了就跳出循环
if #females &= 2 then break end
end end --如果在敌人中只找到了被决斗者,且友方不只有我一个人 if (#females==1) and #self.friends_noself&0 then
self:log(&Only 1&)
first = females[1]--如果敌方有空城诸葛亮且空城诸葛亮决斗伤害对被决斗者有效if zhugeliang_kongcheng and self:damageIsEffective(zhugeliang_kongcheng, sgs.DamageStruct_Normal, females[1]) then--则发起决斗者为诸葛亮table.insert(females, zhugeliang_kongcheng)--如果敌方没有空城诸葛亮或空城诸葛亮决斗伤害对被决斗者无效
else--找出友方杀最多的女性local friend_maxSlash = findFriend_maxSlash(self,first)--如果友方杀最多的女性对被决斗者的决斗造成的伤害有效,则友方杀最多的女性为发起决斗者if friend_maxSlash and self:damageIsEffective(friend_maxSlash, sgs.DamageStruct_Normal, females[1]) then table.insert(females, friend_maxSlash) endendend--如果决斗双方已选好if (#females &= 2) thenfirst = females[1]second = females[2]--获取主公玩家local lord = self.room:getLord()--如果被决斗者体力不大于1if (first:getHp()&=1) then--如果我是主公且身份预知被开启if self.player:isLord() or isRolePredictable() then--找到友方杀最多的女性local friend_maxSlash = findFriend_maxSlash(self,first)--如果存在友方杀最多的女性,决斗发起者为友方杀最多的女性
if friend_maxSlash then second=friend_maxSlash end--如果我不是主公或身份预知未开启,且主公是女性且没有无言
elseif (lord:getGeneral():isFemale()) and (not lord:hasSkill(&wuyan&)) then--如果我是反贼,且被决斗者不是主公,且主公对被决斗者的决斗伤害有效,则决斗发起者为主公if (self.role==&rebel&) and (not first:isLord()) and self:damageIsEffective(lord, sgs.DamageStruct_Normal, first) thensecond = lord --如果我不是反贼或被决斗者不是主公 else--如果我是忠或内,且被决斗者没有刚烈、恩怨,且被决斗者杀的数量小于决斗发起者,则决斗发起者为主公if ((self.role==&loyalist& or (self.role==&renegade&) and not (first:hasSkill(&ganglie&) and first:hasSkill(&enyuan&))))and ( self:getCardsNum(&Slash&, first)&=self:getCardsNum(&Slash&, second)) thensecond = lordendendendend--如果决斗双方不为空,则指定卡牌使用目标if first and second thenif use.to thenuse.to:append(first)use.to:append(second)endenduse.card=cardendendend话说这个技能的AI是我照离间的AI改的,其实就是把性别和变量名改了,其他的都没动,确实考虑的相当周到,不过我感觉damageIsEffective这个函数貌似用错了。因为在smart-ai.lua中函数定义是function SmartAI:damageIsEffective(player, nature, source),也就是说第一个参数是被伤害者,第三个参数是伤害者,感觉在这个技能的AI中两者写反了。不过对于绝大多数情况这个函数的返回值都是true,所以结果都一样。请专业编写AI的前辈们看一下这里是不是写错了。第四类技能编写的通式是:sgs.ai_skill_invoke.skillname = function(self, data)判断是否触发技能,是则return true,否则return falseend例子是以下这个武将的技能—销魂:[&yangguo&] = &杨过&,[&luazhongjian&] = &重剑&,[&luaxiaohun&] = &销魂&,[&:luazhongjian&] = &当你使用杀时对方需使用一张闪加一张杀来闪避。&,[&:luaxiaohun&] = &当你对女性或女性对你造成伤害时,被伤害者需判定,若结果为红色该教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)96_太阳神三国杀diy伤害无效否则该伤害+1。&,--判断触发技是否该触发的函数sgs.ai_skill_invoke.luaxiaohun = function(self, data)--如果友方有改判能力则发动if self:hasWizard(self.friends) then return true end--如果敌方有改判能力则发动if self:hasWizard(self.enemies) then return false end--获取伤害者和受伤者 local damage = data:toDamage() local from = damage.from local to
= damage.to--如果我是伤害者,且受伤者体力小于2或伤害值大于1,则不发动if from:objectName() == self.player:objectName() and (to:getHp() & 2 or damage.damage & 1) thenreturn falseend--如果我是受伤者,且我的体力大于2则不发动if to:objectName() == self.player:objectName() and self.player:getHp() & 2 thenreturn falseend--其他情况都发动return trueend这里需要说明一点,有些触发技的发动频率是Frequent,比如洛神、枭姬、英姿、连营,一般来说我们不必对这类技能写AI。但是有些技能是例外,比如天妒和奸雄,如果是屎的话就不要这个牌,这里多了一些判断,因此需要写AI。以下代码就是奸雄的AI:sgs.ai_skill_invoke.jianxiong = function(self, data)return not sgs.Shit_HasShit(data:toCard())end第五类技能编写是在smart-ai.lua的cardProhibit和slashProhibit中添加技能AI的代码,如果不添加这部分代码的话,lua写出来的禁止技只对人有效,AI照样无视你的技能。杀以外其他卡牌的禁止技在cardProhibit中添加相应代码,比如写一个技能叫“吝啬”,你不能成为过河拆桥的目标,那么就应该像红色代码这样写:function SmartAI:cardProhibit(card, to)if card:inherits(&Slash&) then return self:slashProhibit(card, to) endif card:getTypeId() == sgs.Card_Trick thenif card:isBlack() and to:hasSkill(&weimu&) then return true endif card:inherits(&Indulgence&) or card:inherits(&Snatch&) and to:hasSkill(&qianxun&) then return true endif card:inherits(&Duel&) and to:hasSkill(&kongcheng&) and to:isKongcheng() thenreturn true endif card:inherits(&Dismantlement&) and to:hasSkill(&linse&) then return true end
endreturn falseend杀的禁止技要在slashProhibit中添加相应代码,比如实现一个技能“黑盾”,你不能成为黑色杀的目标,那么就应该在slashProhibit函数中添加以下代码:if card:isBlack() and self:hasSkill(“heidun”) then return true endslashProhibit这个函数是用来让AI不把杀用在某目标身上,例如若只有一张杀是基本牌,我就不会杀刘禅;我手牌少体力也少,那我就不会杀夏侯惇;我不会杀有明闪的敌方张角等。也就是说,这个函数完成了对禁止技、无效技和威慑技的AI。第六类技能的编写,对于杀的处理在slashProhibit里已经讲过了,建议大家认真读一下这个函数。对于其他技能,比如无言,这个就比较麻烦,在每个锦囊的使用函数useCardTrickName,判断AOE是否有效的函数aoeIsEffective,是否使用锦囊的函数useTrickCard、判断锦囊是否有效hasTrickEffective、获取使用优先级的函数getUsePriority中都有对它的考虑。从这里可以看出,要想把AI做好,全面的考虑是非常繁琐但是又必要的。这里提供几个函数及其功能,对于这类技能大家可以在这些函数里添加代码:slashIsEffective:判断杀是否有效,比如黑杀对于禁和仁王盾damageIsEffective:判断伤害是否有效,比如非雷属性伤害对大雾aoeIsEffective:判断AOE是否有效,比如无言、鸡肋、祸首、巨象、帷幕、藤甲 hasTrickEffective:判断锦囊是否有效,比如无言、智迟第七类技能的编写,就是不能把某牌当作它原来用,只能当作锁定技锁定的牌来用,高顺酒当杀,神关羽红桃当杀。这是通过smart-ai.lua中的prohibitUseDirectly和isCompulsoryView来完成的。比如我写一个技能“好战”,所有的延时类锦囊锁定视为决斗,那么它的AI就使用红色代码部分实现:local function prohibitUseDirectly(card, player)if player:hasSkill(&jiejiu&) then return card:inherits(&Analeptic&)elseif player:hasSkill(&wushen&) then return card:getSuit() == sgs.Card_Heartelseif player:hasSkill(&ganran&) then return card:getTypeId() == sgs.Card_Equipelseif player:hasSkill(“haozhan”) then return card: inherits(&DelayedTrick&)endendlocal function isCompulsoryView(card, class_name, player, card_place)local suit = card:getSuitString()local number = card:getNumberString()local card_id = card:getEffectiveId()if class_name == &Slash& and card_place ~= sgs.Player_Equip thenif player:hasSkill(&wushen&) and card:getSuit() == sgs.Card_Heart then return (&slash:wushen[%s:%s]=%d&):format(suit, number, card_id) endif player:hasSkill(&jiejiu&) and card:inherits(&Analeptic&) then return(&slash:jiejiu[%s:%s]=%d&):format(suit, number, card_id) endelseif class_name == “DelayedTrick” thenif player: hasSkill(&haozhan&) then return (&duel:haozhan[%s:%s]=%d&):format(suit, number, card_id) endendend第八类技能中对于出杀次数的限制是在smart-ai.lua的slashIsAvailable中添加代码: 例子是以下这个武将的技能—有剑,红色代码是这个技能的AI:[&dgqb&] = &独孤求败&,[&luawujian&] = &无剑&,[&luayoujian&] = &有剑&,[&luayoujiandev&] = &有剑&,[&luaqiubai&] = &求败&,[&luaqiubaix&] = &求败&,[&:luawujian&] = &当你没有装备武器时你的杀不可被闪避。&,[&:luaqiubai&] = &回合内你可以将任意武器牌当决斗使用。若决斗成功则你可以摸两张牌否则对方摸两张牌&,[&:luayoujian&] = &当你装备有武器时本回合你可以出X张杀,X为你当前的攻击范围,回合内你使用的所有杀都将被计数(不包括决斗出的杀)。&,function SmartAI:slashIsAvailable(player)player = player or self.playerif player:hasFlag(&tianyi_failed&) or player:hasFlag(&xianzhen_failed&) then return false endif player:hasWeapon(&crossbow&) or player:hasSkill(&paoxiao&) thenreturn trueendif player:hasSkill(&luayoujian&) and player:getWeapon() thenreturn (player:usedTimes(&Slash&) + player:usedTimes(&FireSlash&) + player:usedTimes(&ThunderSlash&)) & player:getAttackRange()endif player:hasFlag(&tianyi_success&) thenreturn (player:usedTimes(&Slash&) + player:usedTimes(&FireSlash&) + player:usedTimes(&ThunderSlash&)) & 2elsereturn (player:usedTimes(&Slash&) + player:usedTimes(&FireSlash&) + player:usedTimes(&ThunderSlash&)) & 1endend对出杀距离和指定目标数的修改是在smart-ai.lua中的getTurnUse或useBasicCard中添加相应代码,这里我就不举例了,大家看一下我下面列出的代码应该就会写了。两者有所不同,像天义拼点成功是一种状态,这种状态与使用什么牌无关,所以在getTurnUse中有这样一段代码:if self.player:hasFlag(&tianyi_success&) thenslashAvail = 2self.slash_targets = 2self.slash_distance_limit = trueend而像武神无视距离出杀、方天最后一张牌三杀这些效果与卡牌有关,因此要写在useBasicCard里,虎牢关神吕布第二形态的神戟我认为写在getTurnUse中是比较合理的,但是实际上是在useBasicCard里:if card:getSkillName() == &wushen& then no_distance = true endif (self.player:getHandcardNum() == 1and self.player:getHandcards():first():inherits(&Slash&)and self.player:getWeapon()and self.player:getWeapon():inherits(&Halberd&))or (self.player:hasSkill(&shenji&) and not self.player:getWeapon()) thenself.slash_targets = 3end第九类是响应事件的AI,这是在smart-ai.lua中的askForCard中实现,比如我对敌方的法正造成了伤害,那我宁愿掉血也不会给他红桃的桃,吕布杀我而我只有一张闪,那我肯定不出(别跟我提老诸葛为了空城、邓艾为了屯田可以出,我说的是普遍情况,当然如果把这些都实现了,那AI真是太NB了,这个需要神杀AI的编写者们慢慢积累,不是一天两天能达到的)。例子是对以下这个武将的技能—重剑的响应:[&yangguo&] = &杨过&,[&luazhongjian&] = &重剑&,[&luaxiaohun&] = &销魂&,[&:luazhongjian&] = &当你使用杀时对方需使用一张闪加一张杀来闪避。&,[&:luaxiaohun&] = &当你对女性或女性对你造成伤害时,被伤害者需判定,若结果为红色该伤害无效否则该伤害+1。&,红色代码部分是对响应这个技能添加的AI,可以看到下面还有对无双和肉林响应的AI function SmartAI:askForCard(pattern, prompt, data)…………elseif pattern == &jink& then--如果我没有杀,那我连第一张闪也不必出if parsedPrompt[1] == &@luazhongjian-jink-1& and self:getCardsNum(&Slash&) & 1 then return &.& endif (parsedPrompt[1] == &@wushuang-jink-1& or parsedPrompt[1] == &@roulin1-jink-1& or parsedPrompt[1] == &@roulin2-jink-1&)and self:getCardsNum(&Jink&) & 2 then return &.& end…………在一开始我们还说到有些技能需要在几个选项中做选择,或者是选择目标发动效果。 对于实现在几个选项中做选择的AI的通式是:sgs.ai_skill_choice.skillname = function(self, choices)if 选第1个选项的条件 then return “代表选项1的字符串”elseif …………elseif选第N个选项的条件then return “代表选项N的字符串”endreturn “默认选项”end对于实现选择目标发动效果的AI的通式是:sgs.ai_skill_playerchosen.skillname = function(self, targets)local target找到最适合发动效果的目标,为target赋值return targetend例子是凌统发动旋风时的选项和法正眩惑给牌对玩家的选择:sgs.ai_skill_choice.xuanfeng = function(self, choices)--对敌人按防御强度排序self:sort(self.enemies, &defense&)--创建一个虚拟的无属性杀local slash = sgs.Card_Parse((&slash[%s:%s]&):format(sgs.Card_NoSuit, 0))--遍历所有敌人for _, enemy in ipairs(self.enemies) do--若敌人与我距离不大于1,就选择对距离为1的人造成伤害if self.player:distanceTo(enemy)&=1 thenreturn &damage&--若无属性杀对敌人有效,就选择无视距离出杀elseif not self:slashProhibit(slash ,enemy) thenreturn &slash&endend--否则选择什么都不做return ¬hing&endsgs.ai_skill_playerchosen.xuanhuo = function(self, targets)--遍历所有玩家for _, player in sgs.qlist(targets) do--如果玩家手牌数不大于2或体力小于2,且他是我的友方--且他不是被眩惑的人,则给他眩惑来的牌if (player:getHandcardNum() &= 2 or player:getHp() & 2) and self:isFriend(player) and not player:hasFlag(&xuanhuo_target&) thenreturn player教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)96_太阳神三国杀diyendendend说完了如何编写AI,就该说一下对AI测试的方法,因为AI是用lua编写的,不能像cpp那样有编译环境给你debug,因此经过反复的实验,我了一些可以当作debug的方法和技巧:1. 游戏打不开,提示lua第XXX行有错误:根据这个行数和文件改你的语法错误去吧。2. 你写AI的那个武将不会主动出牌了,每回合就是摸牌弃牌:技能的AI里出问题了,一般是调用了一个不存在的函数,或者是对空对象进行访问。这时你应该打开三国杀程序à启动服务器à启动游戏à再做一次测试,在服务器的输出里会看到:lua/ai/错误所在文件名.lua:XX行: attempt to call method '某函数' (a nil value)根据这个线索我们就可以找到是哪里出错误了。3. 有时测试为了判断AI是否跑进了某个条件语句,我们可以在那个条件语句里加入以下代码,这个代码会在游戏的记录框(就是那个显示XX)里输出你给的字符串,如果显示了正确的字符串,那么肯定跑进对应的条件语句里了。local log = sgs.LogMessage()log.type = 你想要输出的字符串room:sendLog(log)4. 视作技的测试方法:***获取足够你要测试的卡牌,或创造好视作技能够发动的条件(我表示自定义小场景就是测试神器),然后托管看AI能不能发动技能。5. 触发技的测试方法:人工操作手动触发,当弹出对话框提示是否发动技能时,托管看AI的选择是否符合你的预期。6. 无效技、威慑技、禁止技的测试方法:创造好你需要的条件,通常需要使用获取卡牌拿顺拆把AI的手牌拆干净,然后你要测试的武将配刘备双将,把要测试的牌给AI,然后弃牌到AI的回合看AI的表现。7. 常用来配双将的测试型武将:刘备、二张、姜维,话说我还用cpp写了个能观看别人手牌的锦囊,用来测试确实方便终于把太阳神三国杀的CPP和LUA都研究完了,最后总结一下:武将的DIY:我倾向用CPP实现,对于比较底层的修改(如卡牌的使用方法、攻击范围的变化、新的触发事件)CPP有很大优势,因为CPP可以实现的比较完美,只要尽量避免耦合,在原版的代码上修改时做好标记,保证代码的整洁就可以。卡牌的DIY:貌似没见过用LUA来实现卡牌的,而且卡牌的实现比武将要底层得多,果断CPP不解释。AI的编写:虽然CPP可以写一些AI,而且我记得宇文天启有个帖子就是简单介绍这个的,但是毕竟LUA已经非常完善并且是主流,所以果断LUA。最后祝愿大家在DIY方面好点子越来越多,编程实现越来越顺利。本帖结束,OVER~欢迎您转载分享:
更多精彩:

参考资料

 

随机推荐