diff --git a/.gitignore b/.gitignore index 6afa8e1..fdd5f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ nosetests.xml #others _mine +_pending *.exe release *.pyc diff --git a/db/card.tw.txt b/db/card.tw.txt index 0cf7494..8825cff 100644 --- a/db/card.tw.txt +++ b/db/card.tw.txt @@ -266,11 +266,14 @@ 343,第二型庫拉奇,5,12,努力最大程度地發揮自身長處的自戀狂。喜歡大大的貓眼和虎牙。最近沉迷於蘿莉服裝。有著完全理解自身價值進行行動的充滿心計的一面……。,TakeSomebodyLive,術火/奪盡燃萌,攻擊力上升100% 345,第二型多米南,5,16,對黑魔術非常感興趣的女性。是喜歡做表面功夫的類型、經常身穿給人感覺是孝服的黑棕色服裝、塗著鮮紅的口紅。對黑魔術有強烈的感情,若是可能甚至想當魔女的弟子,然而總是沒有機會。,DartBloodArt,術暗/射圓慘血,自己的HP越高\nHP回復 348,米庫拉姆,5,20,在『斷絕時代』和現在都一如既往,喜歡茶會勝過一切、過著與茶會夥伴閒聊的生活。雖然經常被認為是無害的,但她們之間的閒話會在一天之內傳遍整個不列顛,必須小心不要成為談論的對象。閒聊的話題裡也有些是毫無根據的傳言。,貴婦人優雅的午後時光,Error ky62bb9,回復卡片HP100% +352,魔裝型亞瑟-劍術之城-,6,25,製作成與亞瑟相同外表的騎士、結合了人造人的因子。氣色不好、滿是縫合痕跡的臉和身體無法感受到痛楚和人的溫暖。智力不太發達、口齒不清沉默寡言。手持六角鉗子,遇到外敵就會不顧一切地痛毆敵人。因為不是本人,所以無法使用王者之劍。,PatchWorkKnockDown,毆打叩=我武者羅,攻擊力上升250% 353,魔裝型蘭斯洛特,5,14,製作成與蘭斯洛特相同外表的騎士、結合了人工造出的可怕假面男的因子。穿得就像馬上要去參加舞會一樣,戴著可怕的假面。傳言白色的假面將人誘至死亡,為人所恐懼。比起戰鬥更喜歡社交舞,是個沒有騎士樣子的騎士。,DeathmaskGiveoff,舞踏會=死誘仮面,對方HP越低\n攻擊力提升 354,魔裝型小狼女烏莉特,6,25,製作成與小狼女烏莉特相同外表的騎士、結合了人工造出的惡魔因子。保留了狼的耳朵和尾巴,新配備了惡魔的角和翅膀。惡魔之槍是詛咒之槍、以此刺傷外敵,傷口會腐爛、緩慢奪走生命。喜歡惡作劇,有時會半開玩笑地刺向騎士和人類。亦正亦邪。,SexyDevilThrust,有角棘=惡魔小獸,攻擊力上升100% 362,魔裝型高文,5,17,製作成與高文外表相同的騎士、交配了人工作出的狼男的因子。真正的高文在陽光下可以提升力量,而相對地,這個高文擁有在月光下提升力量的能力。據說聽到他吼叫的人類會因為恐懼而癱軟不起……。,RoarMoonLight,月光狼吠#40,自己HP越低\n攻擊力提升 363,魔裝型莫德雷德,5,19,製作成與莫德雷德相同外表的騎士、結合了人工作出的德古拉因子。相對于普通的德古拉喜歡年輕女性的血液,他被設定為喜歡懷有惡意的人的血液。飲血過量的話會喝醉、變得多話。,SnatchBloodPlus,吸血滿杯#98,回復卡片HP100% +364,魔裝型提妮亞,6,23,製作成與提妮亞外表相同的騎士、結合了人工作出的木乃伊因子。能隨意操縱纏在身上的繃帶,任意改變長短粗細。與靦腆的本人不同,喜歡出風頭。,SkeletonBondage,透明包帯#27,回復卡片HP200% 365,第二型伊阿喬,5,20,由湖製造出的最初,是個缺乏戰鬥能力的騎士。對掉落在經常獨自玩耍的地方的銀杏抱有興趣,經過研究後成功提取出了銀杏的那股臭味,製作出了臭氣炸彈。當初目的只是為了惡作劇,但因為強大的效果超過預期,現在成為了與外敵進行戰鬥的騎士。,GinkoRideBombs,銀杏爆彈#51,攻擊力上升200% +372,魔裝型亞瑟-魔法之派-,6,22,製作成與亞瑟外表相同的騎士、結合了人工作出的魔王因子。魔王之杖能夠喚來雷擊。不僅是外敵、就算傷害人類也不會抱有罪惡感,因此跟他接觸時一定要小心。順便一提,因為跟本人不同,所以沒法使用王者之劍。,ThunderLightFinish,術暗/雷轟光殺,自己HP越低\n攻擊力提升(大) 373,魔裝型羅恩格林,5,16,製作成與羅恩格林外表相同的騎士、結合了人工作出的墮天使因子。長著漆黑的翅膀,手持黑亮的丘比特之箭。天使的丘比特之箭孕育愛情,但墮天使的箭預兆了死亡和疾病。,DarkCupidLagoon,術暗/墮天使矢,HP在50%以下時\n攻擊力上升100% 374,魔裝型君士坦丁,6,19,製作成與君士坦丁外表相同的騎士、結合了人工作出的貓娘因子。貓耳貓尾,長得實在可愛,但隱藏著銳利的爪子。性情不定、心情不好時連己方也會攻擊,讓人困擾的性格。意外地很親近亞瑟。,CatNailTear,術風/切裂爪貓,卡片等級MAX時\n攻擊力上升100% 386,異界型基里爾,5,13,從空無一物的空間裡,突然出現的門的地方,發現了能提取的因子,以此為基礎製造出的騎士。為戰鬥的命運所迷惑、擁有自由增減體內的微小電磁波、進行操縱的特殊能力。性格粗魯、自我中心。,PrimitiveCode,人間無骨#1,攻擊力上升50% @@ -318,7 +321,7 @@ 622,異界型御坂美琴 -黒子的偷拍-,5,15,各位都在看我嗎?這是我珍藏的姐姐大人在頂樓休息的偷拍照。\n學園都市的180萬名學生中,只有7位超能力者(第五級)的其中一人。那就是我,白井黑子敬愛的御坂美琴姐姐大人唷。,Afternoonbreak,充電時間,攻擊力上升100% 623,異界型禁書目錄 -修道服解放-,5,19,英國清教第零聖堂區『必要之惡教會』所屬的修女。正式名稱是『Index-Librorum-Prohibitorum』。身著的修道服是由純白色的布以及金色刺繡在上面的「移動教會」。只穿了一個安全別針在上面,當遇到了衝擊……,Cast Off,靈裝解放,HP在30%以下時\n回復卡片HP200% 626,異界型御坂美琴&禁書目録 -共同出擊-,5,11,有名的大小姐學校常盤台中學的14歲女中學生-御坂美琴、與英國清教第零聖堂區『必要之惡教會(Necesserous)』所屬的修女、禁書目録(Index)。,RaligunSpellIncept,超電磁砲&強制詠唱,戰鬥勝利後一定機率\n獲得經驗值1.5倍 -627,異界型史提爾=馬格努斯,5,18,英國清教『必要之惡教會(Necesserous)』所屬的魔法師。14歲卻能完全解析現存的盧恩符文,另外創出6個新福文的天才魔術師。 經常與神裂火織搭檔。魔法名是『在此證明我是最強的理由(Fortis931)』。會使用教皇級威力的『女巫獵人之王(Inocentius)』等強力攻撃魔法。,Inocentius,女巫獵人之王,自己的BC上限越高\n攻擊力提升(大) +627,異界型史提爾=馬格努斯,5,18,英國清教『必要之惡教會(Necesserous)』所屬的魔法師。14歲卻能完全解析現存的盧恩符文,另外創出6個新符文的天才魔術師。 經常與神裂火織搭檔。魔法名是『在此證明我是最強的理由(Fortis931)』。會使用教皇級威力的『女巫獵人之王(Inocentius)』等強力攻撃魔法。,Inocentius,女巫獵人之王,自己的BC上限越高\n攻擊力提升(大) 639,異界型當麻&禁書目録 -灰村清孝ver-,6,25,英國清教第零聖堂區『必要之惡教會(Necesserous)』所屬的修女。正式名稱是「Index-Librorum-Prohibitorum」。,ImagineBreaker--SpellIntercept,幻想殺手&強制詠唱,回復卡片HP200% 640,異界型御坂美琴&禁書目録,5,21,有名的大小姐學校常盤台中學的14歲女中學生-御坂美琴、與英國清教第零聖堂區『必要之惡教會(Necesserous)』所屬的修女、禁書目録(Index)。彼此都對當麻有特殊的感情。,DoubleHeroine,相思相愛,SUPER越多\n攻擊力提升(大) 642,異界型妹達,6,22,以御坂美琴DNA譜做成的複製人。\n外觀雖然相似,但是缺乏感情表現。操縱電氣的能力也遠遠不及本體。,LadiesOfNoise,缺陷電力,HP在50%以下時\n攻擊力上升200% @@ -434,4 +437,6 @@ 967,異界型佐天&菲布理,6,17,佐天淚子是柵川中學一年級,初春的同班同學兼好友。無能力者,對能力者有著強烈的憧憬。留著及肩的黑色長髮,髮飾是櫻花。菲布理擁有一頭金色長髮及兩根呆毛,喜歡吃奇妙口味的棒棒糖。,Water Shooting Gun,術水/水槍射擊,對方HP越低\n攻擊力提升(特大) 976,林默娘,5,18,由遠東古老的神明幻化在異世界登場的少女,身邊總是跟著兩只可愛的小靈體,分別有能夠看得很遠以及能更聽到很遠傳來的聲音的能力。少女對任何人都充滿著親切及慈愛感,使許多人對少女有崇拜之心意。,Lin Mo Niang,守護海洋,自己HP越低\n攻擊力提升(大) 977,雪花型小紅豆,5,18,以距離不列顛很遠的海上所漂流的救船舶發現的書籍為根據而製作的騎士。外表看起來很喜歡親近其他人,但實際上並非如此。總是在非刻意的情況之下使喚其他人。,High And Mighty,盛氣凌人# 11,對方HP越高\n攻擊力提升(大) -978,第二型思嘉古兒,5,14,從北海漂流物中抽出的因子所製成的騎士,對戰鬥有著極度狂熱,總是第一個衝上前線。口頭禪是:「一切為了末日之戰!」但什麼是末日之戰本人卻早已遺忘。戰鬥以外唯一感興趣的事情是酒宴,斟酒手腕意外的高明。,plasma beam breakthrough,滅龍槍=電離通貫,好友越多\n攻擊力提升(大) \ No newline at end of file +978,第二型思嘉古兒,5,14,從北海漂流物中抽出的因子所製成的騎士,對戰鬥有著極度狂熱,總是第一個衝上前線。口頭禪是:「一切為了末日之戰!」但什麼是末日之戰本人卻早已遺忘。戰鬥以外唯一感興趣的事情是酒宴,斟酒手腕意外的高明。,plasma beam breakthrough,滅龍槍=電離通貫,好友越多\n攻擊力提升(大) +979,異界型傑克,6,18,由外大陸來到不列顛的男子,個性開朗活潑且熱情,擁有特殊的技能-駕駛熱氣球的技術,喜歡悠閒漂浮在天空的感覺。受到一些女性的歡迎。,Fly High,翱翔天際#860,攻擊力上升200% +980,異界型蕾娜,6,15,由外大陸來到不列顛的女子,性格活潑且有人氣,受到鎮上的人們喜愛,雖然打扮的並不是很華麗,但是清秀脫俗的氣質惹人喜愛。,Vagetable Basket,菜籃園=清新脫俗,攻擊回數越多\nHP回復(特大) \ No newline at end of file diff --git a/db/item.cn.txt b/db/item.cn.txt index 9f971c5..25e66ce 100644 --- a/db/item.cn.txt +++ b/db/item.cn.txt @@ -1,7 +1,7 @@ 1,AP回复药,回复所有AP 2,BC回复药,回复所有BC 3,假卡,如果对没有被抢走的碎片使用假卡,可以阻止一次碎片被抢走。 -4,次元因子(880MC->580MC),次元碎片中提取的特殊因子,使用后可重置已分配的AP及BC点数 +4,次元因子,次元碎片中提取的特殊因子,使用后可重置已分配的AP及BC点数 5,凹版相片,大量收集的话・・・ 42,东南枝,千辛万苦收集来,不如自挂东南枝 44,秘籍残页,感觉隐藏着什么奇怪的东西 diff --git a/db/item.tw.txt b/db/item.tw.txt index cee2955..fcb1d73 100644 --- a/db/item.tw.txt +++ b/db/item.tw.txt @@ -29,4 +29,5 @@ 65,金彈珠,會帶來好運的金色彈珠・・・ 66,鐵罐空瓶,據說是某位少年與少女們喝完的飲料的罐子・・・ 67,御茶園點數,御茶園x亞瑟王活動點數 -68,七彩輝石,收集的話據說可以到不列顛的某個神祕商店兌換・・・ \ No newline at end of file +68,七彩輝石,收集的話據說可以到不列顛的某個神祕商店兌換・・・ +69,南瓜燈籠,收集到萬聖節的南瓜燈籠的話・・・ \ No newline at end of file diff --git a/db/revision.txt b/db/revision.txt index f93ad4b..d03ee6c 100644 --- a/db/revision.txt +++ b/db/revision.txt @@ -1,3 +1,3 @@ -cn,201,201 -tw,183,160 +cn,201,202 +tw,187,162 jp,0,0 \ No newline at end of file diff --git a/maclient.py b/maclient.py index 3f6aa13..9e76fd2 100644 --- a/maclient.py +++ b/maclient.py @@ -13,6 +13,7 @@ import locale import base64 from xml2dict import XML2Dict +from xml2dict import object_dict import random try: import ConfigParser @@ -25,7 +26,7 @@ import maclient_logging import maclient_smart import maclient_plugin -__version__=1.60 +__version__=1.61 #CONSTS: EXPLORE_BATTLE,NORMAL_BATTLE,TAIL_BATTLE,WAKE_BATTLE=0,1,2,3 GACHA_FRIENNSHIP_POINT,GACHAgacha_TICKET,GACHA_11=1,2,4 @@ -168,6 +169,7 @@ def load_config(self): if (self._read_config('system','enable_plugin') or '1')=='1': disabled_plugin=self._read_config('plugin','disabled').split(',') plugin.set_disable(disabled_plugin) + plugin.scan_hooks() else: plugin.enable=False @@ -360,6 +362,31 @@ def tasker(self,taskname='',cmd=''): logging.error('set_card need 1 argument') else: self.set_card(task[1]) + elif task[0]=='autoset' or task[0]=='as': + aim,fairy,maxline,testmode,delta,includes,infbc='MAX_CP',None,1,True,1,[],False + for arg in task[1:]: + if arg.startswith('aim:'): + aim=arg[4:] + elif arg.startswith('fairy:'): + fairy=object_dict() + fairy.lv,fairy.hp,nothing=map(lambda x:int(x),(arg[6:]+',-325').split(',')) + if nothing!=-325: + fairy.IS_WAKE=False + else: + fairy.IS_WAKE=True + aim='DEFEAT' + elif arg.startswith('line:'): + maxline=int(arg[5:]) + elif arg=='notest': + testmode=False + elif arg=='infbc': + infbc=True + elif arg.startswith('delta:'): + delta=float(arg[6:]) + elif arg.startswith('incl:'): + includes=map(lambda x:int(x),arg[5:].split(',')) + aim=getattr(maclient_smart,aim) + self.invoke_autoset(aim=aim,fairy_info=fairy,maxline=maxline,testmode=testmode,delta=delta,includes=includes,infbc=infbc) elif task[0]=='explore' or task[0]=='e': self.explore(' '.join(task[1:])) elif task[0]=='factor_battle' or task[0]=='fcb': @@ -515,44 +542,77 @@ def check_strict_bc(self,refresh=False): return False @plugin.func_hook - def set_card(self,deckkey): + def invoke_autoset(self,aim=maclient_smart.MAX_CP,includes=[],maxline=2,seleval='card.lv>45',fairy_info=None,delta=1,testmode=True,infbc=False): + return self.set_card('auto_set',aim=aim,includes=includes,maxline=maxline,seleval=seleval,fairy_info=fairy_info,delta=delta,testmode=testmode,infbc=infbc) + + @plugin.func_hook + def set_card(self,deckkey,**kwargs): if deckkey=='no_change': logging.debug('set_card:no_change!') return False - try: - cardid=self._read_config('carddeck',deckkey) - except AttributeError: - logging.warning(du8('set_card:忘记加引号了?')) - return False - if cardid==self._read_config('record','last_set_card'): - logging.debug('set_card:card deck satisfied, not changing.') - return False - if cardid =='': - logging.warning(du8('set_card:不存在的卡组名?')) - return False - cardid=cardid.split(',') - param=[] - last_set_bc=0 - leader_card=0 - for i in xrange(len(cardid)): - if cardid[i]=='empty': - param.append('empty') - leader_card+=1 - elif len(cardid[i])>3: - try: - mid=self.player.card.sid(cardid[i]).master_card_id - except IndexError: - logging.error(du8('你木有sid为 %s 的卡片'%(cardid[i]))) - else: - last_set_bc+=int(self.carddb[int(mid)][2]) - param.append(cardid[i]) + elif deckkey=='auto_set': + testmode=kwargs.pop('testmode') + infbc=kwargs.pop('infbc') + res=maclient_smart.carddeck_gen(self.player.card,bclimit=infbc and 9999 or self.player.bc['current'],**kwargs) + if len(res)==5: + atk,hp,last_set_bc,sid,mid=res + param=map(lambda x:str(x),sid) + #别看比较好 + print(du8('设置卡组为: ATK:%d HP:%d COST:%d\n%s'%( + sum(atk), + hp, + last_set_bc, + '\n'.join( + map(lambda x,y: '%s\tATK:%-5d'%(x,y), + ['|'.join(map( + lambda x:' %-12s'%self.carddb[x][0], + mid[i:min(i+3,len(mid))] + )) + for i in range(0,len(mid),3) + ],#卡组分成一排排 + atk + ) + ) + ) + )) else: - c=self.player.card.cid(cardid[i]) - if c!=[]: - param.append(c[-1].serial_id) - last_set_bc+=int(self.carddb[int(cardid[i])][2]) + logging.error(du8(res[0])) + return False + if testmode: + return False + else: + try: + cardid=self._read_config('carddeck',deckkey) + except AttributeError: + logging.warning(du8('set_card:忘记加引号了?')) + return False + if cardid==self._read_config('record','last_set_card'): + logging.debug('set_card:card deck satisfied, not changing.') + return False + if cardid =='': + logging.warning(du8('set_card:不存在的卡组名?')) + return False + cardid=cardid.split(',') + param=[] + last_set_bc=0 + for i in xrange(len(cardid)): + if cardid[i]=='empty': + param.append('empty') + elif len(cardid[i])>3: + try: + mid=self.player.card.sid(cardid[i]).master_card_id + except IndexError: + logging.error(du8('你木有sid为 %s 的卡片'%(cardid[i]))) + else: + last_set_bc+=int(self.carddb[int(mid)][2]) + param.append(str(cardid[i])) else: - logging.error(du8('你木有id为 %s (%s)的卡片'%(cardid[i],self.carddb[int(cardid[i])][0]))) + c=self.player.card.cid(cardid[i]) + if c!=[]: + param.append(str(c[-1].serial_id)) + last_set_bc+=int(self.carddb[int(cardid[i])][2]) + else: + logging.error(du8('你木有id为 %s (%s)的卡片'%(cardid[i],self.carddb[int(cardid[i])][0]))) noe=','.join(param).replace(',empty','').replace('empty,','').split(',') lc=random.choice(noe) t=5+random.random()*len(noe)*0.7 @@ -802,7 +862,7 @@ def _explore_floor(self,area,floor=None): logging.info(du8('获得了因子碎片 湖:%s 碎片:%s'%( info.parts_one.lake_id,info.parts_one.parts.parts_num))) if len(ct)>10000: - logging.indfo(du8('收集碎片合成了新的骑士卡片!')) + logging.info(du8('收集碎片合成了新的骑士卡片!')) else: logging.warning(du8('AP不够了TUT')) if not self.green_tea(self.cfg_auto_explore): @@ -942,6 +1002,7 @@ def _sell_card(self,serial_id): return False if self._dopost('card/exchange',postdata='mode=1')[0]['error']: return False + serial_id=map(lambda x:str(x),serial_id)#to string while len(serial_id)>0: #>30张要分割 if len(serial_id)>30: @@ -952,7 +1013,7 @@ def _sell_card(self,serial_id): serial_id=serial_id[len(se_id):] #卖 paramsell='serial_id=%s'%(','.join(se_id)) - slp=random.random()*15+7 + slp=random.random()*4+len(se_id)*0.6+2 logging.sleep(du8('%f秒后卖卡……'%slp)) time.sleep(slp) resp,ct=self._dopost('trunk/sell',postdata=paramsell) @@ -1221,7 +1282,7 @@ def fairy_floor(f=fairy): hpleft=int(XML2Dict().fromstring(ct).response.body.explore.fairy.hp) logging.info(du8('YOU LOSE- - Fairy-HP:%d'%hpleft)) #立即尾刀触发,如果补刀一次还没打死,就不打了-v- - if self.cfg_fairy_final_kill_hp>=hpleft and not bt_type==TAIL_BATTLE: + if self.cfg_fairy_final_kill_hp>=hpleft and (not bt_type==TAIL_BATTLE or hpleft<5000): need_tail=True #金币以及经验 logging.info(du8('EXP:+%d(%s) G:+%d(%s)'%( @@ -1292,10 +1353,7 @@ def fairy_floor(f=fairy): self._write_config('fairy',fairy.serial_id, '%d,%.0f'%(int(fairy.time_limit)+int(float(time.time())),time.time())) #等着看结果 - if need_tail: - time.sleep(random.randint(4,10)) - else: - time.sleep(random.randint(8,15)) + time.sleep(random.randint(8,15)) #领取收集品 if nid!=[]: res=self._get_rewards(nid) diff --git a/maclient_cli.py b/maclient_cli.py index df211c1..dcf5a6b 100644 --- a/maclient_cli.py +++ b/maclient_cli.py @@ -17,7 +17,7 @@ import maclient_logging import maclient_remote import getpass -__version__=1.60 +__version__=1.61 #look out for ironpython du8=sys.platform.startswith('cli') and \ (lambda str:str) or\ diff --git a/maclient_player.py b/maclient_player.py index 19d4f99..6057ed1 100644 --- a/maclient_player.py +++ b/maclient_player.py @@ -140,19 +140,23 @@ def load_db(self,loc): self.db[int(c[0])]=[c[1],int(c[2]),int(c[3])] def update(self,carddict): - self.cards=carddict + self.cards=[] + for p in carddict: + self.cards.append(p) + for elem in p:#store as int + self.cards[-1][elem]=int(getattr(p,elem)) self.count=len(self.cards) #print self.cid('124') def _found_card_by_value(self,key,value): res=[] for i in self.cards: - if i[key].value==value: + if i[key]==value: res.append(i) return res def sid(self,sid): - return self._found_card_by_value('serial_id',sid)[0] + return self._found_card_by_value('serial_id',int(sid))[0] def cid(self,cid): - return self._found_card_by_value('master_card_id',cid) + return self._found_card_by_value('master_card_id',int(cid)) def check_exclusion(inpstr): '''Return False if exclusion exists''' diff --git a/maclient_plugin.py b/maclient_plugin.py index a762b68..5a7c015 100644 --- a/maclient_plugin.py +++ b/maclient_plugin.py @@ -20,14 +20,16 @@ class plugins(): def __init__(self,logger): self.logger=logger - #所有插件名称 - self.plugins=[] + #所有插件模块对象 + self.plugins={} + #所有插件模块中的plugin实例 + self.plugins_instance={} #新增cli命令字典 self.extra_cmd={} #从maclient实例映射的变量 self.val_dict={} self.load_plugins() - self.scan_hooks() + #self.scan_hooks() self.enable=True @@ -39,41 +41,46 @@ def scan_hooks(self): #scan plugin hooks for p in self.plugins: #extra cmd - ecmd=self._get_plugin_meta(p,'extra_cmd') + ecmd=self._get_module_meta(p,'extra_cmd') for e in ecmd: - self.extra_cmd[e]=self._get_plugin_meta(p,ecmd[e]) + self.extra_cmd[e]=self._get_module_meta(p,ecmd[e]) #function hook for act in ALL_ACTIONS: for method in [PREF_ENTER,PREF_EXIT]:#enter, exit key='%s%s'%(method,act) if key not in self.hook_reg: self.hook_reg[key]={} - if key in self._get_plugin_meta(p,'hooks'):#add to hook reg - self.hook_reg[key][p]=self._get_plugin_meta(p,'hooks')[key] + if key in self._get_module_meta(p,'hooks'):#add to hook reg + #priority record + self.hook_reg[key][p]=self._get_module_meta(p,'hooks')[key] # def set_enable(self,lst): # pass def set_maclient_val(self,val_dict): self.val_dict=val_dict def do_extra_cmd(self,cmd): - return self.extra_cmd[cmd](self.val_dict)() + if self.enable: + return self.extra_cmd[cmd](self.val_dict)() + else: + self.logger.warning('Plugins not enabled.') def set_disable(self,lst): for p in lst: - if p: - del(self.plugins[self.plugins.index(p)]) - self.scan_hooks() + if p and (p in self.plugins): + del(self.plugins[p]) - def _get_plugin_meta(self,mod,key): + def _get_module_meta(self,mod,key): + #module.xxx try: - return getattr(globals()[mod],key) + return getattr(self.plugins[mod],key) except AttributeError: - self.logger.warning('No meta "%s" found in module "%s"'%(key,mod)) + self.logger.warning('"%s" not found in module "%s"'%(key,mod)) return [] def _get_plugin_attr(self,mod,attr): + #module.plugin.xxx try: - getattr(globals()[mod].plugin(),attr) + return getattr(self.plugins_instance[mod],attr) except AttributeError: self.logger.warning('Get "%s" failed from "%s" '%(attr,mod)) return [] @@ -101,8 +108,14 @@ def load_plugins(self): if m.startswith('_'): continue if m not in self.plugins: - globals()[m]=__import__(m) - self.plugins.append(m) + #module object + self.plugins[m]=__import__(m) + #plugin instance + try: + self.plugins_instance[m]=self.plugins[m].plugin() + except AttributeError: + #no plugin() class + self.plugins_instance[m]=None def _line_tracer(self): #originally from http://stackoverflow.com/questions/19227636/decorator-to-log-function-execution-line-by-line @@ -126,6 +139,7 @@ def func_hook(self,func): def do(*args, **kwargs): if self.enable: ret=self._do_hook('%s%s'%(PREF_ENTER,func.__name__),*args, **kwargs) + args,kwargs=ret ret=func(*args, **kwargs) self._do_hook('%s%s'%(PREF_EXIT,func.__name__)) return ret diff --git a/maclient_smart.py b/maclient_smart.py index e93fe2a..5e94762 100644 --- a/maclient_smart.py +++ b/maclient_smart.py @@ -5,6 +5,7 @@ # fffonion import time import math +import itertools #server specified configutaions max_card_count_cn=max_card_count_kr=200 max_card_count_tw=max_card_count_jp=250 @@ -21,7 +22,7 @@ #wake name_wake_rare=['禁書目錄'] name_wake=name_wake_rare+['觉醒','覺醒','雷蒂麗'] -#snda gplus +#snda gplus, not working class snda_gplus(): #thanks to luw2007(https://github.com/luw2007/libMA/blob/master/push.py) #心跳时间 @@ -48,9 +49,6 @@ def gen_android_id(seed=time.time()): def gen_imei(seed=time.time()): pass -#card_deck generator -def carddeck_gen(player): - pass #platform specified config enhancer class config_changer(): @@ -91,9 +89,150 @@ def fairy_atk(cls,lv,type=0): ''' #data required #普妖(一般)有两种,另外有醒妖和稀有妖 - pass + return (int(lv)-2)*400 + +def _defeat(fairy,delta,show=False): + def _detail(side): + sides=['FAIRY','YOU'] + print('%-5s:'%(sides[side])) + fatk=calc.fairy_atk(fairy.lv,type=fairy.IS_WAKE) + fhp=fairy.hp + if show: + detail=_detail + else: + detial=lambda x:None + ####↑以上为静态数据###↓以下为卡组计算数据###### + def do(atkl,hp,rnd): + #global t1 + #t1-=time.time() + #计算当妖精打死玩家时玩家对妖精的伤害 + dmg=0 + for one_atk in itertools.cycle(atkl): + dmg+=one_atk + if dmg>=fairy.hp*delta: + #t1+=time.time() + return True + hp-=fatk + if hp<=0: + #t1+=time.time() + return False + return do +HP,ATK,LV,MID,SID=0,1,2,3,4 + +def _carddeck_info(cards): + ''' + 卡组的atk,hp,排数 + ''' + #卡组攻击 + _hp=sum(map(lambda d:d[HP],cards)) + #卡组攻击轮数 + _rnd=int(math.ceil(1.0*len(cards)/3)) + #卡组攻击 + _atk=[0,]*_rnd + for i in range(_rnd): + #每排的攻击 + _atk[i]=sum(map(lambda d:d[ATK],cards[i*3:min(i*3+3,len(cards))])) + return _atk,_hp,_rnd + +#card_deck generator +DEFEAT,MAX_DMG,MAX_CP=0,1,2 +def carddeck_gen(player_cards,aim=DEFEAT,bclimit=999,includes=[],maxline=2,seleval='True',fairy_info=None,delta=1): + ''' + 自动配卡 + aim 目标 + bclimit 最大BC + seleval 优选卡 + !includes 必须有这些卡(card对象) + player_cards cards实例 + fairy_info 妖精信息,hp,lv + range 允许误差(预测伤害相对于妖精血量) + maxline 最大排数 + ''' + #print(aim,bclimit,includes,maxline,seleval,fairy_info,delta) + #只需要hp,atk,lv,cost,master_card_id,serial_id,object_dict->list节省20%时间 + _cards=[( + card.hp, + card.power, + card.lv, + card.master_card_id, + card.serial_id) + for card in player_cards.cards if eval(seleval)]#减少待选卡片数 + print('Selecting cards from %s candidates...'%len(_cards)) + _iter_gen=lambda cardnum=3:itertools.combinations(_cards,cardnum) + _sumup=lambda a,b:a+b + atkpw=lambda d:d[HP]+d[ATK] + cp=lambda d:1.0*(d[HP]+d[ATK])/player_cards.db[d[MID]][2] + resdict=[] + if aim==DEFEAT: + if not fairy_info: + return ['没有输入妖精信息'] + dcalc=_defeat(fairy_info,delta,show=False) + for deckcnt in [1,2]+[i*3 for i in range(1,maxline+1)]: + last_failed=0 + for deck in _iter_gen(deckcnt): + mids=map(lambda d: d[MID],deck) + if deckcnt!=len(list(set(mids))):#有重复卡的跳过 + continue + _cost=sum(map(lambda e:player_cards.db[e][2],mids)) + if _cost>bclimit:#BC太多了跳过 + continue + _atk,_hp,_rnd=_carddeck_info(deck) + if last_failed>(sum(_atk)*_hp): + continue + if dcalc(_atk,_hp,_rnd):#看能不能打败 + sids=map(lambda d: d[SID],deck) + #print sids + resdict.append([1.0*(sum(_atk)+_hp)/_cost,_atk,_hp,_cost,sids,mids]) + else: + if last_failed<(sum(_atk)*_hp): + last_failed=(sum(_atk)*_hp) + #若当前数量的卡组能满足要求,则不找更多的卡了 + if resdict: + break + #COST最小,其次看CP + return_lambda=lambda x:(-x[3],x[0]) + elif aim==MAX_DMG or aim==MAX_CP: + if aim==MAX_DMG: + deckcnts=[i*3 for i in range(maxline,0,-1)] + return_lambda=lambda x:(sum(x[0])*x[1]) + _cards=sorted(_cards,key=lambda x:x[ATK]*x[HP],reverse=True)[:min(3*maxline+3,len(_cards))] + else: + deckcnts=[1,2]+[i*3 for i in range(1,maxline+1)] + return_lambda=lambda x:(1.0*(x[1]*sum(x[0]))/x[2]) + _cards=sorted(_cards,key=lambda x:x[ATK]*x[HP]/player_cards.db[x[MID]][2],reverse=True)[:min(3*maxline+3,len(_cards))] + for deckcnt in deckcnts: + for deck in _iter_gen(deckcnt): + mids=map(lambda d: d[MID],deck) + _cost=sum(map(lambda e:player_cards.db[e][2],mids)) + if bclimit>=_cost: + _atk,_hp,_rnd=_carddeck_info(deck) + sids=map(lambda d: d[SID],deck) + resdict.append([_atk,_hp,_cost,sids,mids]) + # for r in resdict: + # print r,','.join(map(lambda e: player_cards.db[e][0],r[2])) + #返回cost,sid,mid + #错误时返回[errmsg] + if resdict: + print ('Found %d suitable carddeck(s).'%len(resdict)) + r=max(resdict,key=return_lambda) + return r[-5:] + else: + return ['未能选出符合条件的卡组'] + if __name__=='__main__': - print calc.fairy_hp(1,calc.NORMAL_FAIRY) - print calc.items_get(7,calc.WAKE_FAIRY,505956) \ No newline at end of file + #print calc.fairy_hp(1,calc.NORMAL_FAIRY) + #print calc.items_get(7,calc.WAKE_FAIRY,505956) + import maclient + from xml2dict import object_dict + import time + fairy=object_dict() + fairy.lv,fairy.hp,fairy.IS_WAKE=20,200000,False + mac=maclient.maClient(configfile=r'D:\Dev\Python\Workspace\maClient\_mine\config_tw.ini') + mac.initplayer(open(r'D:\Dev\Python\Workspace\maClient\.tw-532554.playerdata','r').read()) + #t1=0 + t2=time.time() + print carddeck_gen(mac.player.card,aim=MAX_DMG,maxline=1,bclimit=250,fairy_info=fairy,seleval='card.lv>45 or card.master_card_id in [124,59,8]') + #print t2',time.time()-t2 + #raw_input() \ No newline at end of file diff --git a/plugins/_prototype.py b/plugins/_prototype.py index 1142e3c..2d048ab 100644 --- a/plugins/_prototype.py +++ b/plugins/_prototype.py @@ -1,3 +1,4 @@ +#import dbm class plugin_prototype(): def __init__(self): self.hooks={} @@ -11,6 +12,9 @@ def tuple_assign(self,ori_tuple,index,val): new[int(index)]=val return tuple(new) + def setval(self,key,val): + pass#print self.__name__ + # @classmethod # def __call__(self,func): # raise NotImplementedError("%s is called, but not implemented."%func) \ No newline at end of file diff --git a/plugins/bgm.py b/plugins/bgm.py new file mode 100644 index 0000000..b474630 --- /dev/null +++ b/plugins/bgm.py @@ -0,0 +1,171 @@ +#coding:utf-8 +from _prototype import plugin_prototype +import pymedia.audio.acodec as acodec +import pymedia.audio.sound as sound +import pymedia.muxer as muxer +import os,os.path as opath +import sys +import threading +import time +#start meta +__plugin_name__='bgm player' +__author='fffonion' +__version__=0.1 +#hooks={'ENTER/EXIT_ACTION':PRIORITY} +#eg: +#hook on explore start with priority 1 (the bigger the higher): +#'ENTER_explore':1 +hooks={'ENTER__fairy_battle':1,'EXIT__fairy_battle':1,'ENTER__explore_floor':1,'EXIT__explore_floor':1,'ENTER_tasker':1,'EXIT_tasker':1} +#extra cmd hook +extra_cmd={'bgm on':'set_bgm_on','bgm off':'set_bgm_off'} +#end meta +getPATH0=(opath.split(sys.argv[0])[1].find('py') != -1 or sys.platform=='cli') \ + and sys.path[0].decode(sys.getfilesystemencoding()) \ + or sys.path[1].decode(sys.getfilesystemencoding()) + +def set_bgm_off(plugin_vals): + snd = sound.Output(5,1,sound.AFMT_S16_LE)#whatever + def do(): + fade_out(snd) + return do + +def set_bgm_on(plugin_vals): + snd = sound.Output(5,1,sound.AFMT_S16_LE) + def do(): + fade_in(snd) + return do + +def fade_out(snd,duration=0.5): + if snd: + for i in xrange(25): + snd.setVolume(65535*(24-i)/25) + time.sleep(duration/25.0) + +def fade_in(snd,duration=0.5): + if snd: + for i in xrange(25): + snd.setVolume(65535*(i+1)/25) + time.sleep(duration/25.0) + +def play_sound_from_file(file_name): + p=music_player(file_name) + p.setDaemon(True) + p.start() + return p + +def get_abs_path(cmp_path): + return opath.join(getPATH0,cmp_path).encode('utf-8') + +class music_player(threading.Thread): + def __init__(self,mfile): + threading.Thread.__init__(self) + self.mfile=mfile + self.stop=False + self.pos=0 + self.buf=None + + def getbuff(self,csize=512): + #get buf loop so that playing can last long + if not self.buf: + f = open(self.mfile, 'rb') + self.buf=f.read() + if self.pos+csize>len(self.buf): + ret=self.buf[self.pos:] + self.pos=0 + else: + ret=self.buf[self.pos:self.pos+csize] + self.pos+=csize + return ret + + def run(self): + dm = muxer.Demuxer(str.split(self.mfile, '.')[-1].lower()) + self.snd = dec = None + s = self.getbuff() + while len(s) and not self.stop: + frames = dm.parse(s) + if frames: + for fr in frames: + if dec == None: + dec = acodec.Decoder(dm.streams[fr[0]]) + r = dec.decode(fr[1]) + if r and r.data: + if self.snd == None: + self.snd = sound.Output( + int(r.sample_rate), + r.channels, + sound.AFMT_S16_LE) + data = r.data + self.snd.play(data) + s = self.getbuff() + #淡出 + fade_out(self.snd) + #停止 + self.snd.stop() + #设置音量正常 + self.snd.setVolume(65535) + + +class plugin(plugin_prototype): + def __init__(self): + self.__name__=__plugin_name__ + self.state=0 + self.last_state=[0] + self.playing=None + self.state_file=['plugins/bgm/bgm_common1.mp3','plugins/bgm/bgm_sarch1.mp3','plugins/bgm/bgm_event1.mp3'] + + def _change_state(self,state): + #change state + self.last_state.append(self.state)#stack push + if self.playing and state !=self.state: + self.playing.stop=True + self.playing.join() + self.state=state + self.playing=play_sound_from_file(get_abs_path(self.state_file[state])) + return True + elif not self.playing: + self.playing=play_sound_from_file(get_abs_path(self.state_file[state])) + else: + return False + + def _rollback_state(self): + #take this as simple "stack pop" + if len(self.last_state)<1: + self.last_state=[0] + self._change_state(self.last_state[-1]) + self.last_state=self.last_state[:-2] + + def ENTER_tasker(self,*args, **kwargs): + self._change_state(0) + + def ENTER__explore_floor(self,*args, **kwargs): + self._change_state(1) + + def ENTER__fairy_battle(self,*args, **kwargs): + self._change_state(2) + + EXIT__fairy_battle=EXIT__explore_floor=EXIT_tasker=_rollback_state + + +if __name__=='__main__': + #cd .. + getPATH0=opath.split(getPATH0)[0] + a=plugin() + a.setval(1,2) + a.ENTER_tasker() + time.sleep(2) + a.ENTER__explore_floor() + time.sleep(2) + snd = sound.Output(5,1,sound.AFMT_S16_LE) + snd.setVolume(0) + time.sleep(3) + snd.setVolume(65535) + a.ENTER__fairy_battle() + a.ENTER__fairy_battle() + time.sleep(10) + a.EXIT__fairy_battle() + time.sleep(2) + a.EXIT__explore_floor() + time.sleep(2) + a.EXIT_tasker() + #a.ENTER_explore() + time.sleep(120) \ No newline at end of file diff --git a/plugins/bgm/bgm_common1.mp3 b/plugins/bgm/bgm_common1.mp3 new file mode 100644 index 0000000..a38bc52 Binary files /dev/null and b/plugins/bgm/bgm_common1.mp3 differ diff --git a/plugins/bgm/bgm_event1.mp3 b/plugins/bgm/bgm_event1.mp3 new file mode 100644 index 0000000..fb327fa Binary files /dev/null and b/plugins/bgm/bgm_event1.mp3 differ diff --git a/plugins/bgm/bgm_sarch1.mp3 b/plugins/bgm/bgm_sarch1.mp3 new file mode 100644 index 0000000..c8e7491 Binary files /dev/null and b/plugins/bgm/bgm_sarch1.mp3 differ diff --git a/plugins/hehe.py b/plugins/hehe.py index 8242ef3..c339d03 100644 --- a/plugins/hehe.py +++ b/plugins/hehe.py @@ -1,6 +1,6 @@ from _prototype import plugin_prototype #start meta -__name__='sample plugin' +__plugin_name__='sample plugin' __author='fffonion' __version__=0.1 #hooks={'ENTER/EXIT_ACTION':PRIORITY} @@ -12,6 +12,8 @@ extra_cmd={} #end meta class plugin(plugin_prototype): + def __init__(self): + self.__name__=__plugin_name__ def ENTER_explore(self,*args, **kwargs): print 'explore!' diff --git a/plugins/pymedia/__init__.py b/plugins/pymedia/__init__.py new file mode 100644 index 0000000..f394cdb --- /dev/null +++ b/plugins/pymedia/__init__.py @@ -0,0 +1,33 @@ +## Copyright (C) 2002-2003 Dmitry Borisov +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Library General Public +## License as published by the Free Software Foundation; either +## version 2 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Library General Public License for more details. +## +## You should have received a copy of the GNU Library General Public +## License along with this library; if not, write to the Free +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## + +""" +PyMedia is a set of Python modules designed to be used in media centers. +Small footprint, fast and reliable library for embedded environments +For more examples and documentation use the tutorial section on pymedia web site: +http://pymedia.org/tut/ +""" + +__all__= [ 'muxer', 'audio', 'video', 'removable' ] +import muxer, audio +#import video, removable +from player import Player +from meta import getMetaData + +muxer.error = muxer.MuxerError +muxer.extensions= muxer.audio_extensions+ muxer.video_extensions +__version__= "1.3.7.0" \ No newline at end of file diff --git a/plugins/pymedia/audio/__init__.py b/plugins/pymedia/audio/__init__.py new file mode 100644 index 0000000..35d3acd --- /dev/null +++ b/plugins/pymedia/audio/__init__.py @@ -0,0 +1,33 @@ +## Copyright (C) 2002-2003 Dmitry Borisov +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Library General Public +## License as published by the Free Software Foundation; either +## version 2 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Library General Public License for more details. +## +## You should have received a copy of the GNU Library General Public +## License along with this library; if not, write to the Free +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## + +""" +Modules to deal with the audio data. It includes: +- wave input/output +- resampling +- encoding +- decoding""" + +__all__= [ 'acodec', 'sound' ] +import acodec, sound + +try: + acodec.error = acodec.ACodecError + sound.error = sound.SoundError +except: + # no worry if cannot import new style Exceptions + pass \ No newline at end of file diff --git a/plugins/pymedia/meta.py b/plugins/pymedia/meta.py new file mode 100644 index 0000000..5e21f0c --- /dev/null +++ b/plugins/pymedia/meta.py @@ -0,0 +1,48 @@ + +import os, traceback +import pymedia + +HEADER_CHUNK_SIZE= 30000 +FREQUENCY_KEY= 'sample_freq' +CHANNELS_KEY= 'channels' +BITRATE_KEY= 'bitrate' + +# ----------------------------------------------------- +# Get metadata from the media file +def getMetaData( f, ext= None ): + info= {} + if type( f ) in ( str, unicode ): + ext= f.split( os.path.extsep )[ -1 ] + f= open( f, 'rb' ) + + try: s= f.read( HEADER_CHUNK_SIZE ) + except: traceback.print_exc(); return info; + + ext= ext.lower() + dm= pymedia.muxer.Demuxer( ext ) + fr= dm.parse( s ) + if len( dm.streams )== 0: + return {} + + dec= pymedia.audio.acodec.Decoder( dm.streams[ 0 ] ) + # Decode only first frame to get frame parameters + d= dec.decode( fr[ 0 ][ 1 ] ) + if d and ( dm.hasHeader()> 0 or len( d.data )> 0 ): + # Assign parameters to the file + info.update( { FREQUENCY_KEY: d.sample_rate, + CHANNELS_KEY: d.channels, + BITRATE_KEY: d.bitrate / 1000 } ) + + # Hardcoding for mp3 + if not dm.hasHeader() and ext== 'mp3': + try: + f.seek( -128, 2 ) + dm= pymedia.muxer.Demuxer( ext ) + dm.parse( f.read( 128 ) ) + except: + traceback.print_exc(); + + if dm.hasHeader(): + info.update( dm.getHeaderInfo() ) + + return info diff --git a/plugins/pymedia/player.py b/plugins/pymedia/player.py new file mode 100644 index 0000000..faee85f --- /dev/null +++ b/plugins/pymedia/player.py @@ -0,0 +1,846 @@ + +import sys, thread, time, traceback + +import pymedia.muxer as muxer +import pymedia.audio.acodec as acodec +#import pymedia.video.vcodec as vcodec +#import pymedia.audio.sound as sound + +SEEK_IN_PROGRESS= -2 +PAUSE_SLEEP= 0.003 +FILE_CHUNK= 300000 +EXITING_FLAG=-1 + +########################################################################3 +# Simple video player +class Player: + """ + Player is a class that plays all audio/video formats supported by pymedia + It provides very simple interface and many features such as: + - seeking + - extracting metadata + - multiple sound cards support + - different sources for video rendering + - error handling + - volume operations + + Here is the simple way of calling Player: + player= pymedia.Player() + player.start() + player.startPlayback( '' ) + while player.isPlaying(): + time.sleep( 0.01 ) + + Player supports the following callback: + class Callback: + # called _before_ the audio frame is rendered + # return your audio data after you process the audio frame + # or return none for using the default sound processing + def onAudioReady( self, afr ): + pass + + # Called when the video frame is about to be rendered + def onVideoReady( self, vfr ): + pass + + Use the callback like below: + c= Callback() + player= pymedia.Player( c ) + """ + # ------------------------------------ + def __init__( self, callback= None ): + """ + Create new Player instance. In case if you want to play video you have to supply the callback class instance + which will get video data and will be able to display it properly ( onVideoReady() call ). + For audio data the playback is done through the pymedia.sound.Output object. + Before playing audio the onAudioReady( afr ) callback is called so you can modify audio data. + Just return None if you do not wish to modify that data or return a sound data as string in case if you do. + Remember that sampling frequency and number of channels should remain the same as in afr object. + """ + self.frameNum= -1 + self.exitFlag= 1 + self.ct= None + self.pictureSize= None + self.paused= 0 + self.snd= None + self.aDelta= 0 + self.aBitRate= 0 + self.vBitRate= 0 + self.seek= -1 + self.vc= None + self.ac= None + self.ovlTime= 0 + self.playingFile= None + self.endPos= None + self.loops= 0 + self.volume= 0xffff + self.startPos= 0 + self.fileSize= 0 + self.initADelta= -1 + # Set length to 1 sec by default + self.length= -1 + # By default the first audio card + self.audioCard= 0 + self.callback= callback + self.metaData= {} + self.fileFormat= 'mp3' + self.aPTS= self.aindex= self.vindex= -1 + self.clearError() + self.maxBufSize= -1 + + # ------------------------------------ + def _resetAudio( self ): + # No delta for audio so far + self.snd= self.resampler= None + self.aBitRate= self.aSampleRate= self.aChannels= self.aDelta= 0 + self.aDecodedFrames= [] + if self.ac: + self.ac.reset() + + # ------------------------------------ + def _initAudio( self, params ): + try: + # Reset audio stream + self._resetAudio() + # Initializing audio codec + self.ac= acodec.Decoder( params ) + self.aindex= -1 + except: + self.err.append( sys.exc_info() ) + + # ------------------------------------ + def _resetVideo( self ): + # Init all used vars first + self.decodeTime= self.vBitRate= self.frameNum= \ + self.sndDelay= self.hurry= self.videoPTS= \ + self.lastPTS= self.frRate= self.vDelay= 0 + self.seek= -1 + self.pictureSize= None + if self.initADelta!= -1: + self.seekADelta= self.initADelta + + # Zeroing out decoded pics queue + self.decodedFrames= [] + self.rawFrames= [] + if self.vc: + self.vc.reset() + + # ------------------------------------ + def _initVideo( self, params ): + # There is no overlay created yet + try: + # Set the initial sound delay to 0 for now + # It defines initial offset from video in the beginning of the stream + self.initADelta= -1 + self.vindex= -1 + self._resetVideo() + self.seekADelta= 0 + # Setting up the HW video codec + self.vc= pymedia.video.ext_codecs.Decoder( params ) + except: + try: + # Fall back to SW video codec + self.vc= vcodec.Decoder( params ) + except: + pass + + # ------------------------------------ + def _getStreamLength( self, format, dm, f, fr ): + # Get file size if possible + pos= f.tell() + f.seek( 0, 2 ) + self.fileSize= f.tell() + f.seek( pos, 0 ) + + # Demux frames from the beginning and from the end and get the PTS diff + if not dm.hasHeader(): + startPTS= -1 + for d in fr: + if d[ 3 ]> 0: + startPTS= d[ 3 ] / 90 + break + + # Seek to the end and get the PTS from the end of the file + if startPTS> 0: + pos= f.tell() + f.seek( 0, 0 ) + dm1= muxer.Demuxer( format ) + s= f.read( FILE_CHUNK ) + r= dm1.parse( s ) + endPTS= startPTS + for d in fr: + if d[ 3 ]> 0: + endPTS= d[ 3 ] / 90 + + f.seek( pos, 0 ) + self.length= endPTS- startPTS + else: + lStreams= filter( lambda x: x and ( x[ 'length' ]> 0 ), dm.streams ) + if len( lStreams ): + self.length= max( [ x[ 'length' ] for x in lStreams ] ) + else: + self.length= -1 + # Check file size against length and bitrates + #if self.length* ( self.getBitRate()/ 8 )< self.fileSize: + # self.length= self.fileSize/ ( self.getBitRate()/ 8 ) + + # ------------------------------------ + def _getVStreamParams( self, vindex, vparams, r ): + # Decode one video frame and 1 audio frame to get stream data + vDec= None + for d in r: + try: + # Demux file into streams + if d[ 0 ]== vindex: + if not vDec: + vDec= vcodec.Decoder( vparams ) + vfr= vDec.decode( d[ 1 ] ) + if vfr and vfr.data: + if vfr.aspect_ratio> .0: + self.pictureSize= ( int( vfr.size[ 1 ]* vfr.aspect_ratio ), vfr.size[ 1 ] ) + else: + self.pictureSize= vfr.size + + self.vBitRate= vfr.bitrate + break + except: + self.err.append( sys.exc_info() ) + break + + # ------------------------------------ + def _processVideoFrame( self, d, forced= False ): + # See if we should show video frame now + if not forced: + self.rawFrames.append( d ) + if len( self.decodedFrames )== 0: + if self._decodeVideoFrame( forced )== -1: + return + + # See if we have our frame inline with the sound + self._processVideo( forced ) + + # ------------------------------------ + def _decodeVideoFrame( self, forced= False ): + # Decode the video frame + if self.snd== None and self.seek!= SEEK_IN_PROGRESS and self.aindex!= -1 and not forced: + return -1 + + if len( self.decodedFrames )> vcodec.MAX_BUFFERS- 4: + # Cannot decode video frame because of too many decoded frames already + return 0 + + while len( self.rawFrames ): + d= self.rawFrames.pop( 0 ) + vfr= self.vc.decode( d[ 1 ] ) + if vfr: + if self.seek== SEEK_IN_PROGRESS and not forced: + if vfr.data: + self.seek= -1 + else: + # We decode video frame after seek, but no I frame so far + return 0 + + # If frame has data in it, put it in to the queue along with PTS + self.decodedFrames.append( ( vfr, self.videoPTS ) ) + # Set up the video bitrate for the informational purpose + if self.vBitRate== 0: + self.vBitRate= vfr.bitrate + + # Handle the PTS + rate= float( vfr.rate_base )/ vfr.rate + if d[ 3 ]> 0 and self.lastPTS< d[3]: + # Get the first lowest PTS( we do not have DTS :( ) + self.lastPTS= d[3] + self.videoPTS= float( d[ 3 ] ) / 90000 + #print 'VPTS:', self.videoPTS, vfr.pict_type, self.getSndLeft() + else: + # We cannot accept PTS, just calculate it + self.videoPTS+= rate + + return 0 + + # No more raw frames to decode + return -2 + + # ------------------------------------ + def _processVideo( self, forced= False ): + while 1: + if len( self.decodedFrames )== 0: + return + + vfr, videoPTS= self.decodedFrames[ 0 ] + self.vDelay= videoPTS- self.seekADelta- self._getPTS() + frRate= float( vfr.rate_base )/ vfr.rate + res= self._decodeVideoFrame() + #print '<<', res, self.vDelay, self.frameNum, videoPTS, self._getPTS(), len( self.decodedFrames ), len( self.rawFrames ), self._getSndLeft(), self.aindex, self.isPaused() + #if res== -1 or ( self.snd and self.getSndLeft()< frRate ) or ( res== -2 and self.vDelay> 0 and self.getSndLeft()< self.vDelay and not forced ): + if res== -1 or ( res== -2 and self.vDelay> 0 and self._getSndLeft()< self.vDelay and not forced ): + return + + # If audio queue is empty and we still have video frames, add 1 audio frame per every video frame + if self.vDelay> 0 and self._getSndLeft()< 0.01 and self.aindex!= -1 and len( self.aDecodedFrames )== 0 and self._getPTS()> 0: + if self.vDelay> frRate: + self.vDelay= frRate + + #print 'Appending dummy audio...', self.vDelay, frRate + self._appendDummyAudio( self.vDelay ) + time.sleep( self.vDelay ) + self.vDelay= 0 + + # If delay + #print '!!', self.vDelay, self.frameNum, videoPTS, self._getPTS(), len( self.decodedFrames ), len( self.rawFrames ), self._getSndLeft(), self.seekADelta + if self.vDelay> 0 and self._getSndLeft()> self.vDelay: + time.sleep( self.vDelay / 6 ) + #print 'Delay', self.vDelay, self.getSndLeft() + self.vDelay= 0 + + if self.vDelay< frRate / 4 or forced: + # Remove frame from the queue + del( self.decodedFrames[ 0 ] ) + + # Get delay for seeking + if self.frameNum== 0 and self.initADelta== -1: + self.initADelta= self._getSndLeft() + + # Increase number of frames + self.frameNum+= 1 + + # Skip frame if no data inside, but assume it was a valid frame though + if vfr.data: + try: self.callback.onVideoReady( vfr ) + except: pass + + self.vDelay= frRate + + # ------------------------------------ + def _appendDummyAudio( self, length ): + # Get PCM length of the audio frame + l= self.aSampleRate* self.aChannels* 2* length + self.aDecodedFrames.append( ( '\0'* int( l ), self.aSampleRate, self.aChannels ) ) + self._processAudio() + + # ------------------------------------ + def _processAudioFrame( self, d ): + # Decode audio frame + afr= self.ac.decode( d[ 1 ] ) + if afr: + # See if we have to set up the sound + if self.snd== None: + self.aBitRate= afr.bitrate + self.aSampleRate= afr.sample_rate + self.aChannels= afr.channels + try: + # Hardcoded S16 ( 16 bit signed ) for now + #print 'Opening sound', afr.sample_rate, afr.channels, sound.AFMT_S16_LE, self.audioCard + self.snd= sound.Output( afr.sample_rate, afr.channels, sound.AFMT_S16_LE, self.audioCard ) + self.resampler= None + except: + try: + # Create a resampler when no multichannel sound is available + self.resampler= sound.Resampler( (afr.sample_rate,afr.channels), (afr.sample_rate,2) ) + # Fallback to 2 channels + #print 'Falling back to', afr.sample_rate, 2, sound.AFMT_S16_LE, self.audioCard + self.snd= sound.Output( afr.sample_rate, 2, sound.AFMT_S16_LE, self.audioCard ) + except: + self.err.append( sys.exc_info() ) + raise + + # Calc max buf size for better audio handling + if self.maxBufSize== -1: + self.maxBufSize= self.snd.getSpace() + + # Handle the PTS accordingly + snd= self.snd + if d[ 3 ]> 0 and self.aDelta== 0 and snd: + # set correction factor in case of PTS presence + self.aDelta= ( float( d[ 3 ] ) / 90000 )- snd.getPosition()- snd.getLeft() + + # Play the raw data if we have it + if len( afr.data )> 0: + # Split the audio data if the size of data chunk larger than the buffer size + data= afr.data + if len( data )> self.maxBufSize: + data= str( data ) + + while len( data )> self.maxBufSize: + chunk= data[ : self.maxBufSize ] + data= data[ self.maxBufSize: ] + self.aDecodedFrames.append( ( chunk, afr.sample_rate, afr.channels ) ) + + self.aDecodedFrames.append( ( data, afr.sample_rate, afr.channels ) ) + self._processAudio() + + # ------------------------------------ + def _processAudio( self ): + snd= self.snd + while len( self.aDecodedFrames ) and snd: + # See if we can play sound chunk without clashing with the video frame + if len( self.aDecodedFrames[ 0 ][ 0 ] )> snd.getSpace() and self.vindex!= -1: + break + + #print 'player SOUND LEFT:', self.snd.getLeft(), self.snd.getSpace(), self.isPlaying() + if self.isPlaying(): + data, sampleRate, channnels= self.aDecodedFrames.pop(0) + if self.callback: + try: + data1= self.callback.onAudioReady( data, sampleRate, channnels ) + if data1: + data= data1 + except: + pass + + # See if we need to resample the audio data + if self.resampler: + data= self.resampler.resample( data ) + + snd.play( data ) + + # ------------------------------------ + def start( self ): + """ + Start player object. It starts internal loop only, no physical playback is started. + You have to call start only once for the player instance. + If you wish to play multiple files just call startPlayback() subsequently. + """ + if self.ct: + raise 'cannot run another copy of vplayer' + self.exitFlag= 0 + self.pictureSize= None + self.ct= thread.start_new_thread( self._readerLoop, () ) + + # ------------------------------------ + def stop( self ): + """ + Stop player object. It stops internmal loop and any playing file. + Once the internal loop is stopped the further playback is not possible until you issue start() again. + """ + self.stopPlayback() + if self.callback: + try: self.callback.removeOverlay() + except: pass + + # Turn the flag to exist the main thread + self.exitFlag= EXITING_FLAG + + # ------------------------------------ + def startPlayback( self, file, format= 'mp3', paused= False ): + """ + Starts playback of the file passed as string or file like object. + Player should already be started otherwise this call has no effect. + If any file is already playing it will stop the playback and start playing the file you pass. + 'paused' parameter can specifiy if the playback should not start until unpausePlayback() is called. + 'paused' parameter is helpfull when you want to start your playback exactly at a certain time avoiding any delays + caused by the file opening or codec initilizations. + """ + self.stopPlayback() + # Set the new file for playing + self.paused= paused + self.fileFormat= format + self.playingFile= file + try: + self.setVolume( vars.volume ) + except: + pass + + # ------------------------------------ + def stopPlayback( self ): + """ + Stops file playback. If media file is not playing currently, it does nothing. + If file was paused it will unpause it first. + """ + self.playingFile= None + self.unpausePlayback() + if self.snd: + self.snd.stop() + self.snd= None + + # -------------------------------------------------------- + def pausePlayback( self ): + """ Pause playing the current file """ + if self.isPlaying(): + self.paused= 1 + if self.snd: + self.snd.pause() + + # -------------------------------------------------------- + def unpausePlayback( self ): + """ Resume playing the current file """ + if self.isPlaying(): + if self.snd: + self.snd.unpause() + + self.paused= 0 + + # ------------------------------------ + def isPaused( self ): + """ Returns whether playback is paused """ + return self.paused + + # ------------------------------------ + def seekTo( self, secs ): + """ + Seeks the file playback position to a given number of seconds from the beginning. + Seek may position not exactly as specified especially in video files. + It will look for a first key frame and start playing from that position. + In some files key frames could resides up to 10 seconds apart. + """ + while self.seek>= 0: + time.sleep( 0.01 ) + + if secs< 0: + secs= 0 + + self.seek= secs + + # ------------------------------------ + def isPlaying( self ): + """ Returns whether playback is active """ + return self.playingFile!= None and self.isRunning() + + # ------------------------------------ + def isRunning( self ): + """ + Returns whether Player object can do the playback. + It will return False after you issue stop() + """ + return self.exitFlag in ( EXITING_FLAG, 0 ) + + # ------------------------------------ + def getError( self ): + """ + Return error list if any + """ + return self.err + + # ------------------------------------ + def clearError( self ): + """ + Clear all errors + """ + self.err= [] + + # ------------------------------------ + def setLoops( self, loops ): + """ + Set number of loops the player will play current file + """ + self.loops= loops + + # ------------------------------------ + def setStartTime( self, timePosSec ): + """ + Set start time for the media track to start playing. + Whenever file is played it will start from the timePosSec position in the file. + """ + self.startPos= timePosSec + + # ------------------------------------ + def setEndTime( self, timePos ): + """ + Set end time for the media track. + Whenever file is reached the endTime it will stop playing. + """ + self.endPos= timePos + + # ------------------------------------ + def setVolume( self, volume ): + """ + Set volume for the media track to play + volume = [ 0..65535 ] + """ + self.volume= volume + # Asume the very first is the the Master volume + conns= sound.Mixer().getControls() + if len( conns ): + conns[ 0 ][ 'control' ].setValue( self.volume ) + + # ------------------------------------ + def getVolume( self ): + """ + Get volume for the playing media track 0..65535 + """ + # Asume the very first is the the Master volume + conns= sound.Mixer().getControls() + if len( conns ): + return conns[ 0 ][ 'control' ].getValue() + + return 0 + + # ------------------------------------ + def getPictureSize( self ): + """ + Whenever the file containing video is played, this function returns the actual picture size for the + video part. It may be None if no video is found in the media file. + Note: picture size may be unknown for up to 1 second after you call startPlayback() ! + """ + return self.pictureSize + + # ------------------------------------ + def getLength( self ): + """ + Get length of the media file in seconds + Note: length may be unknown for up to 1 second after you call startPlayback() ! + """ + return self.length + + # ------------------------------------ + def getMetaData( self ): + """ + Get meta data from the media file as dictionary + Note: metadata may be unknown for up to 1 second after you call startPlayback() ! + """ + return self.metaData + + # ------------------------------------ + def getSampleRate( self ): + """ + Get sample rate of the playing file + """ + return self.sampleRate + + # ------------------------------------ + def getABitRate( self ): + """ + Get bitrate for the audio stream if present + """ + if self.aindex!= -1: + return self.aBitRate + + return 0 + + # ------------------------------------ + def getBitRate( self ): + """ + Get bitrate for the full stream + """ + return self.getABitRate()+ self.vBitRate + + # ------------------------------------ + def getPosition( self ): + """ + Returns current position for the media + """ + if self.isPlaying(): + return self._getPTS() + + return 0 + + # ------------------------------------ + def _hasQueue( self ): + queue= 0 + if self.aindex!= -1: + queue+= len( self.aDecodedFrames ) + if self.vindex!= -1: + queue+= len( self.rawFrames )+ len( self.decodedFrames ) + + return queue> 0 + + # ------------------------------------ + def _getPTS( self ): + if self.aindex== -1: + if not self.aPTS: + self.aPTS= time.time() + + return time.time()- self.aPTS + + if self.snd== None: + return 0 + + p= self.snd.getPosition() + return p+ self.aDelta + + # ------------------------------------ + def _getSndLeft( self ): + if self.snd== None: + if self.aindex== -1: + return 1 + else: + return 0 + + return self.snd.getLeft() + + # ------------------------------------ + def _readerLoop( self ): + f= None + try: + while self.exitFlag== 0: + if self.playingFile== None: + time.sleep( 0.01 ) + continue + + self.length= self.frameNum= -1 + # Initialize demuxer and read small portion of the file to have more info on the format + self.clearError() + if type( self.playingFile ) in ( str, unicode ): + try: + f= open( self.playingFile, 'rb' ) + format= self.playingFile.split( '.' )[ -1 ].lower() + except: + traceback.print_exc() + self.err.append( sys.exc_info() ) + self.playingFile= None + continue + else: + format= self.fileFormat + f= self.playingFile + + try: + dm= muxer.Demuxer( format ) + s= f.read( FILE_CHUNK ) + r= dm.parse( s ) + except: + traceback.print_exc() + self.err.append( sys.exc_info() ) + self.playingFile= None + continue + + try: self.metaData= dm.getHeaderInfo() + except: self.metaData= {} + + # This seek sets the seeking position already at the desired offset from the beginning + if self.startPos: + self.seekTo( self.startPos ) + + # Setup video( only first matching stream will be used ) + self.clearError() + self.vindex= -1 + streams= filter( lambda x: x, dm.streams ) + for st in streams: + if st and st[ 'type' ]== muxer.CODEC_TYPE_VIDEO: + self._initVideo( st ) + self.vindex= list( streams ).index( st ) + break + + # Setup audio( only first matching stream will be used ) + self.aindex= -1 + self.aPTS= None + for st in streams: + if st and st[ 'type' ]== muxer.CODEC_TYPE_AUDIO: + self._initAudio( st ) + self.aindex= list( streams ).index( st ) + break + + # Open current file for playing + currentFile= self.playingFile + if self.vindex>= 0: + self._getVStreamParams( self.vindex, streams[ self.vindex ], r ) + + self._getStreamLength( format, dm, f, r ) + + # Play until no exit flag, not eof, no errs and file still the same + while len(s) and len( self.err )== 0 and \ + self.exitFlag== 0 and self.playingFile and len( streams ) and \ + self.playingFile== currentFile: + + if self.isPaused(): + time.sleep( PAUSE_SLEEP ) + continue + + for d in r: + if self.playingFile!= currentFile: + break + + # Seeking stuff + if self.seek>= 0: + # Find the file position first + if self.length> 0 and self.fileSize> 0: + #print self.seek, self.length, self.fileSize, ( float( self.seek ) / self.length )* self.fileSize + f.seek( ( float( self.seek ) / self.length )* self.fileSize, 0 ) + else: + f.seek( self.seek* self.getBitRate()/ 8, 0 ) + #print self.seek, self.getBitRate(), f.tell() + + #print 'seek to', self.seek, f.tell() + seek= self.seek + self.aDecodedFrames= [] + if self.ac: + self.ac.reset() + self.snd.stop() + self.rawFrames= [] + self.decodedFrames= [] + if self.vc: + self.vc.reset() + + dm.reset() + if self.vindex== -1: + # Seek immediately if only audio stream is available + self.seek= -1 + self.aDelta= seek + else: + # Wait for a key video frame to arrive + self.seek= SEEK_IN_PROGRESS + break + + # See if we reached the end position of the video clip + if self.endPos and self._getPTS()* 1000> self.endPos: + # Seek at the end and close the reading loop instantly + f.seek( 0, 2 ) + break + + try: + # Update length if not set already + if self.getLength()== -1 and self.getBitRate()> 0: + # Check file size against length and bitrates + self.length= self.fileSize/ ( self.getBitRate()/ 8 ) + + # Demux file into streams + if d[ 0 ]== self.vindex: + # Process video frame + seek= self.seek + self._processVideoFrame( d ) + if self.seek!= SEEK_IN_PROGRESS and seek== SEEK_IN_PROGRESS: + # If key frame was found, change the time position + self.videoPTS= ( float( f.tell() )/ self.fileSize )* self.length + self.aDelta= self.videoPTS+ self.initADelta + #print '---->position', f.tell(), self.aDelta + elif d[ 0 ]== self.aindex and self.seek!= SEEK_IN_PROGRESS: + # Decode and play audio frame + self._processAudioFrame( d ) + except: + traceback.print_exc() + self.err.append( sys.exc_info() ) + self.playingFile= None + break + + # Read next encoded chunk and demux it + try: + s= f.read( 512 ) + r= dm.parse( s ) + except: + traceback.print_exc() + self.err.append( sys.exc_info() ) + self.playingFile= None + continue + + if f: f.close() + + # Close current file when error detected + if len( self.err ): + self.stopPlayback() + + # Wait until all frames are played + while self._hasQueue()> 0 and self.isPlaying(): + self._processVideoFrame( None, True ) + self._processAudio() + + while self.aindex!= -1 and self._getSndLeft()> 0 and self.isPlaying(): + time.sleep( 0.01 ) + + self._resetAudio() + self._resetVideo() + + if self.loops> 0: + self.loops-= 1 + continue + + # Report the file end + try: f= self.callback.onPlaybackEnd + except: f= None + if f: + f( self ) + else: + self.playingFile= None + except: + traceback.print_exc() + + self.exitFlag= 1 diff --git a/plugins/web_helper.py b/plugins/web_helper.py index 9bdde47..99333b2 100644 --- a/plugins/web_helper.py +++ b/plugins/web_helper.py @@ -9,7 +9,7 @@ import urllib2 import _winreg as winreg #start meta -__name__='web broswer helper' +__plugin_name__='web broswer helper' __author='fffonion' __version__=0.2 hooks={} diff --git a/utils/log_anaylyzer.py b/utils/log_anaylyzer.py new file mode 100644 index 0000000..1ec53b6 --- /dev/null +++ b/utils/log_anaylyzer.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# coding:utf-8 +# log analyzer +# Contributor: +# fffonion + +#using sort +#sort -m -t "" -k 1 -o events_merge.log logfile1 logfile2 ... +import os +import re +import sys + +try: + lfile=sys.argv[1] + sys.argv[1] +except IndexError: + print('Usage: log_analyzer.py logfile') + sys.exit(1) + +def data_add(dic,key,val): + val=float(val) + if key in dic: + dic[key][0]=(dic[key][0]*dic[key][1]+val)/(dic[key][1]+1) + dic[key][1]+=1 + else: + dic[key]=[val,1] + +fairys={} +'''妖精:波寇Lv13 hp:157320 发现者:fffonion 小伙伴:0''' +pending_fairy=None +lines=open(lfile).readlines() +for ind in range(len(lines)): + line_ori=lines[ind] + #print line_ori.decode('utf-8').encode('cp936','ignore') + line=line_ori[18:] + if line.startswith('妖精:'): + fname=re.findall('妖精:([^L]+)',line)[0] + try: + lv=re.findall('Lv(\d+)',line)[0] + except IndexError: + continue + #print fname.decode('utf-8').encode('cp936'),fname not in fairys + #raw_input() + if fname not in fairys: + fairys[fname]={} + try: + nakama=re.findall('小伙伴:(\d+)',line)[0] + except IndexError: + pass#broken log + else: + if nakama=='0':#is full hp + #data_add(fairys[fname],'HP,%s'%lv,re.findall('hp:(\d+)',line)[0]) + if 'HP,%s'%lv not in fairys[fname]:#record the first time + fairys[fname]['HP,%s'%lv]=[float(re.findall('hp:(\d+)',line)[0]),1] + pending_fairy=[fname,lv,0] + if pending_fairy: + if line_ori.startswith('ROUND:'): + fname,lv=pending_fairy[:2] + atk=re.findall('ATK\:[\d\.]+\/([\d\.]+)',line_ori)[0] + if atk!='0.0': + data_add(fairys[fname],'ATK,%s'%lv,atk) + pending_fairy=None + else: + if pending_fairy[-1]>=20:#过了20行 + pending_fairy=None + else: + pending_fairy[-1]+=1 +f=open(r'z:\output.csv','w') +for fa in fairys: + f.write(fa.decode('utf-8').encode('gbk')+'\n') + for i in sorted(fairys[fa].items(),key=lambda x:(x[0].split(',')[0],int(x[0].split(',')[1]))): + f.write('%s,%s\n'%(i[0],round(i[1][0],1)))