Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Я тут уже второй день ковыряюсь с таймером. Кажестся проблема не в таймере, он идет нормально и не отстает от серверного, точнее отстает на некоторое примерно постоянное значение.
Возьмем опять же несчастных секов.
у нас есть цикл событий, у меня он 30000мс + ~30мс (на выполнение акторов).
Каждый цикл мы проверяем на входящее сообщение от сервера и получаем его с запазданием (т.к. ждем 30 секунд, а в это время евент на сервере уже готов).
Так вот, если врямя у нас отстает немного, то у нас есть шанс получить евент с jobStartTime большим, чем время бота.
Это происходит с некоторой вероятностью или зависит от положения Луны. Надеюсь не надо объяснять почему? И чем меньше задержка в цикле, тем выше вероятность "попасть" на разнице во времени.
Я думал, что надо подвигать замер времени отправка запроса START - не помогло, добавлял замер отправки запроса TIME - не помогло.
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Сообщение от ruslanische
Я думал, что надо подвигать замер времени отправка запроса START - не помогло, добавлял замер отправки запроса TIME - не помогло.
У меня, вроде как, +2*START-1*TIME+локальное время прихода gamestate считается серверным временем, а разница этого значения с локальным временем - уже ingame time , всё равно неверно. Поэксперементировать же можно?
С разными TIME и START , сразу по отдаче острова посылать рубить и ловить jobStartTime . раз 9 на разных значениях времени TIME, START и разности (да, может быть придётся принудительно подождать между ними для такого) -- и табличку кидаете сюда? Я посчитаю, мне несложно, я заинтересован.
Последний раз редактировалось megabyte0; 20.01.2015 в 21:25.
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Поэксперементировать же можно?
щас делаю мини игру "клиент-сервер по добычи", сымитирую, а то в голове плохо укладывается
Добавлено через 2 часа 12 минут
Уффф, умаялся
Вот механика бага с таймером, может не все учтено, но видно, что при маленьком отставании времени, jobStartTime может быть _рандомно_ больше времени клиента:
Код:
# -*- coding: utf-8 -*-
import time
import random
from threading import Thread
from threading import Lock
MATERIAL_GAIN_TIME = 1000
PING_MIN = 3
PING_MAX = 9
client_lock = Lock()
server_lock = Lock()
client_incoming_events = []
server_incoming_events = []
class Server(Thread):
def __init__(self):
self.materials = [{'id':1,'count': 3}, {'id':2,'count': 3}, {'id':3,'count': 3}]
self.gaining_material = None
self.start_time = time.time()
self.client = None
self.client_start_time = None
self.__events = []
self.jobStarted = False
self.jobStartTime = None
self.jobEndTime = None
Thread.__init__(self)
def run(self):
self.handle_events()
def get_client_time(self):
return self.get_server_uptime() + self.client_start_time
def get_server_uptime(self):
return long((time.time()-self.start_time)*1000)
def check_if_job_done(self):
if self.jobStarted:
if self.jobEndTime <= self.get_client_time():
assert self.gaining_material is not None
assert self.gaining_material['count'] > 0
#print(u'server: материал добыт клиентом')
# материал добыт
self.gaining_material['count'] -= 1
if self.gaining_material['count'] > 0:
# продолжаем добывать
self.continue_job()
else:
#print(u'server: материал закончился')
self.stop_job()
else:
self.return_ping()
def return_ping(self):
sending_event = {
'type':'ping'
}
self.send_events([sending_event])
def stop_job(self):
assert self.gaining_material is not None
#print(u'server: работа остановлена')
sending_event = {
'type': 'gainMaterial',
'action': 'stop',
'id': self.gaining_material['id']
}
self.gaining_material = None
self.jobStarted = False
self.jobEndTime = None
self.jobStartTime = None
self.send_events([sending_event])
def continue_job(self):
assert self.jobStarted
assert self.gaining_material is not None
assert self.gaining_material['count'] > 0
# отправляем евент о продолжении работы
#print(u'server: продолжена добыча: ' + unicode(self.gaining_material['id']))
assert self.jobEndTime is not None
self.jobStartTime = self.jobEndTime
self.jobEndTime = self.jobStartTime + MATERIAL_GAIN_TIME
sending_event = {
'type':'gainMaterial',
'action': 'start',
'jobStartTime':self.jobStartTime,
'jobEndTime':self.jobEndTime,
'id': self.gaining_material['id']
}
self.send_events([sending_event])
def start_new_job(self, material):
if material['count'] <= 0:
print(u'server: ingame error (ресурс уже закончился)')
exit()
# отправляем евент о новой работе
#print(u'server: начата добыча: ' + unicode(material['id']))
self.jobStarted = True
self.jobStartTime = self.get_client_time()
self.jobEndTime = self.jobStartTime + MATERIAL_GAIN_TIME
sending_event = {
'type':'gainMaterial',
'action': 'start',
'jobStartTime':self.jobStartTime,
'jobEndTime':self.jobEndTime,
'id': material['id']
}
self.send_events([sending_event])
def handle_events(self):
while filter(lambda(x): x['count'] > 0, self.materials):
self.thread_safe_receive_events()
if self.client is None:
time.sleep(0.001)
continue
for event in list(self.__events):
if event['type'] == 'ping':
self.check_if_job_done()
self.__events.remove(event)
elif event['type'] == 'gain':
materials = filter(lambda(x): x['id'] == event['id'], self.materials)
if not materials:
print(u'server: ingame error (нет такого ресурса на сервере)')
exit()
self.gaining_material = materials[0]
self.start_new_job(self.gaining_material)
self.jobStarted = True
self.__events.remove(event)
else:
print(u'server: ingame error (не известное событие)')
exit()
continue
time.sleep(0.001)
def set_client(self, client, client_start_time):
self.client_start_time = client_start_time
self.client = client
def send_events(self, events):
assert isinstance(events, list)
global client_lock
global client_incoming_events
# время отправки до клиента
ping = random.randrange(PING_MIN, PING_MAX)/float(1000)
time.sleep(ping)
client_lock.acquire()
client_incoming_events.extend(events)
client_lock.release()
def thread_safe_receive_events(self):
global server_lock
global server_incoming_events
server_lock.acquire()
self.__events.extend(server_incoming_events)
server_incoming_events = []
server_lock.release()
class Client(object):
def __init__(self):
self.materials = [{'id':1,'count': 3}, {'id':2,'count': 3}, {'id':3,'count': 3}]
self.gaining_material = None
self.start_time = time.time()
self.delta_time = 0
self.jobStarted = False
self.jobStartTime = None
self.jobEndTime = None
self.server = None
self.__events = []
def get_client_time(self):
return long(self.get_client_uptime() + self.delta_time)
def get_client_uptime(self):
return long((time.time()-self.start_time)*1000)
def set_server(self, server):
self.server = server
def check_if_job_done(self):
if self.jobStarted and self.gaining_material is not None:
if self.jobEndTime <= self.get_client_time():
delta = int(self.get_client_time() - self.jobEndTime)
print(u'client: добыли материал '+unicode(delta)+u' мс назад ' + u'('+unicode(self.gaining_material['id'])+u')')
assert self.gaining_material['count'] > 0
self.gaining_material['count'] -= 1
if self.gaining_material['count'] <= 0:
self.jobStarted = False
print(u'client: материал закончился ' + u'('+unicode(self.gaining_material['id'])+u')')
self.gaining_material = None
else:
delta = self.jobEndTime - self.get_client_time()
print(u'client: ooops, время на клиенте отстает на '+unicode(delta)+u' мс')
def handle_events(self):
client.thread_safe_receive_events()
for event in self.get_events():
if event['type'] == 'ping':
self.__events.remove(event)
elif event['type'] == 'gainMaterial':
self.check_if_job_done()
if event['action'] == 'stop':
self.jobStarted = False
self.jobStartTime = None
self.jobEndTime = None
self.gaining_material = None
elif event['action'] == 'start':
self.jobStarted = True
self.jobStartTime = event['jobStartTime']
self.jobEndTime = event['jobEndTime']
materials = filter(lambda(x): x['id'] == event['id'], self.materials)
self.gaining_material = materials[0]
assert self.gaining_material['count'] > 0
else:
print(u'client: неизвестный action: ' + event['action'])
exit()
self.__events.remove(event)
else:
print(u'client: неизвестное событие')
exit()
def get_events(self):
return list(self.__events)
def send_events(self, events):
assert isinstance(events, list)
global server_lock
global server_incoming_events
# время отправки до сервера
ping = random.randrange(PING_MIN, PING_MAX)/float(1000)
time.sleep(ping)
server_lock.acquire()
server_incoming_events.extend(events)
server_lock.release()
def thread_safe_receive_events(self):
global client_lock
global client_incoming_events
client_lock.acquire()
self.__events.extend(client_incoming_events)
client_incoming_events = []
client_lock.release()
if __name__ != '__main__':
exit()
random.seed()
server = Server()
server.start()
client = Client()
client.delta_time = 1000
client.set_server(server)
server.set_client(client, client.get_client_time())
# это наше отставание
# lim(delta -> infinite) P = 1,
# P - вероятность рассхинхрона, delta - отставание времени от серверного
client.delta_time -= 9
# Этот тест надо делать на реальном клиенте
# jobStartTime - client_time д.б. в пределах пинга,
# если намного больше, значит надо подкрутить время
if True:
sending_event = {'type':'gain', 'id':1}
client_time = client.get_client_time()
client.send_events([sending_event])
while not client.get_events():
client.thread_safe_receive_events()
time.sleep(0.001)
jobStartTime = client.get_events()[0]['jobStartTime']
print('client_time: ' + str(client_time))
print('jobStartTime time: ' + str(jobStartTime))
print('delta time: ' + str(jobStartTime-client_time))
exit()
# Иначе делаем имитацию добычи
else:
for res in client.materials:
sending_event = {'type':'gain', 'id':res['id']}
client.send_events([sending_event])
while res['count'] > 0:
client.send_events([{'type':'ping'}])
client.handle_events()
time.sleep(0.001)
Последний раз редактировалось ruslanische; 21.01.2015 в 14:21.
Причина: Добавлено сообщение
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Сообщение от Cheater-84
Вопрос к уважаемому Megabyte.
я с вопросом по твоему крафту, правильно ли я понял, так тогда должен выглядеть крафт лопат в глаз алмазе?
А кто будет наличие бамбука проверять? И при чем тут проверка острова? Плюс, хорошо бы сделать проверку, сколько лопат имеется, а иначе нафига на них спускать весь бамбук и все деньги...
Кстати, я у себя еще сделал при посеве так, чтобы можно было в любом месте программы менять глобально, какие семена сеятся (грубо говоря, текущее семя объявлено переменной, которую можно поменять). И когда у меня количество бамбука (или малины) падает ниже определенного количества, я начинаю сеять нужные семена. А когда-то еще делал процедуру "пополнения склада" - если каких либо семян на складе меньше тысячи, начинал сеять их чтобы "их было".
PS. По-поводу нового глобального параметра "time" (за пределами event)... Я не думаю, что ваше sig invalig приходит из-за него. У меня до сих пор руки не дошли его вставить, и у меня игра пока работает без проблем (вернее "как изредка вываливалась, так и вываливается", но работает...)
Последний раз редактировалось mike4kz; 21.01.2015 в 14:56.
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Сообщение от mike4kz
А кто будет наличие бамбука проверять? И при чем тут проверка острова? Плюс, хорошо бы сделать проверку, сколько лопат имеется, а иначе нафига на них спускать весь бамбук и все деньги...
Кстати, я у себя еще сделал при посеве так, чтобы можно было в любом месте программы менять глобально, какие семена сеятся (грубо говоря, текущее семя объявлено переменной, которую можно поменять). И когда у меня количество бамбука (или малины) падает ниже определенного количества, я начинаю сеять нужные семена. А когда-то еще делал процедуру "пополнения склада" - если каких либо семян на складе меньше тысячи, начинал сеять их чтобы "их было".
PS. По-поводу нового глобального параметра "time" (за пределами event)... Я не думаю, что ваше sig invalig приходит из-за него. У меня до сих пор руки не дошли его вставить, и у меня игра пока работает без проблем (вернее "как изредка вываливалась, так и вываливается", но работает...)
Здравствуй mike4kz, я вообще почти не играю и не юзаю бота, просто интересно стало как делать крафты, все же вроде megabyte0 запилил в base, вот я и подумал как будет выглядить крафт, а проверку ....ну можно дописать конечно)
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Сообщение от Cheater-84
Здравствуй mike4kz, я вообще почти не играю и не юзаю бота, просто интересно стало как делать крафты, все же вроде megabyte0 запилил в base, вот я и подумал как будет выглядить крафт, а проверку ....ну можно дописать конечно)
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Сообщение от mike4kz
А кто будет наличие бамбука проверять?
Внутри craft же и проверено уже. Выше сколько писали, что я туда всю обвязку (модификации game_state) и проверки убрал. Вот проверка на исходные итемы, она там повторюсь уже присутствует
Код:
min(self.craft_item_count(material.item)/material.count for material in craft.materials)
Вот как у меня в harvest_exchange выглядит крафт из бамбука:
Код:
need_shovels=10000
shovels=self._get_game_state().count_in_storage('@SHOVEL_EXTRA')
if shovels<5000:#need_shovels:
logger.info(u"Сделали %d лопат"%(self.craft('B_EYE','1', long(math.ceil((need_shovels-shovels)/5.0)))*5))
Но так как такой код стал появляться сильно часто, я запилил craft_to -- сделать до стольки-то (он возвращает сколько в итоге получилось, даже если сделал 0 раз). Плюсом допилил коллекции. В плане -- допилить craft_to_secure , который сравнивает результат крафта из reader с требуемым, и крафтит только если совпадают (ну, меняли же склеп, юнгу убирали, вдруг что ещё заменят) и возвращает tuple из сколько получилось и русского названия скрафченного итема. апдейченный крафт
Код:
def money(self):
return self._get_game_state().get_state().gameMoney
def craft_item_count(self,item):
if item=="@COINS": return self.money()
if item.startswith("@C_"):
item_lstrip=item.lstrip('@')
collectionItems = self._get_game_state().get_state().collectionItems
return 0L if not hasattr(collectionItems,item_lstrip) else getattr(collectionItems,item_lstrip)
return self._get_game_state().count_in_storage(item)
def add_crafted_item(self,item,count):
if item=="@COINS":
self._get_game_state().get_state().gameMoney+=count
return
if item.startswith("@C_"):
item_lstrip=item.lstrip('@')
collectionItems = self._get_game_state().get_state().collectionItems
has = 0L if not hasattr(collectionItems,item_lstrip) else getattr(collectionItems,item_lstrip)
setattr(collectionItems,item_lstrip,has+count)
return
self._get_game_state().add_from_storage(item, count)
def __get_building_craft(self,building,craft_id):
return filter(lambda(obj):obj.id == craft_id,self._get_item_reader().get(building).crafts)[0]
def craft_available(self,building,craft_id):
craft = self.__get_building_craft(building,craft_id)
#проверка на то, тратится или нет
#проверка на макс число сделанных
return min(self.craft_item_count(material.item)/material.count for material in craft.materials)
def craft(self,building,craft_id,count,max_count_once=500):
craft = self.__get_building_craft(building,craft_id)
for item in self._get_game_state().get_state().gameObjects:
if (item.item == "@"+building) and (item.level>=craft.level):
building_id = item.id
break
else:
return 0
count = min(count,self.craft_available(building,craft_id))
if count <= 0:
return 0
saved_count=count
#removing
for material in craft.materials:
self.add_crafted_item(material.item, -material.count*count)
#adding
self.add_crafted_item(craft.result, craft.resultCount*count)
craft_event={"itemId":craft_id,"objId":building_id,"action":"craft","type":"item"}
#sending
events=[craft_event] * min(count,max_count_once)
while count>max_count_once:
self.send(events)
count-=max_count_once
events=[craft_event] * count
self.send(events)
return saved_count
def craft_to(self,building,craft_id,count,max_count_once=500):
craft = self.__get_building_craft(building,craft_id)
has = self.craft_item_count(craft.result)
if has >= count:
return has
craft_count = (count - has)/craft.resultCount
return self.craft(building,craft_id,craft_count,max_count_once)*craft.resultCount+has
def location_id(self):
return self._get_game_state().get_game_loc().get_location_id()
Цитата:
Сообщение от ruslanische
Видно, что в боте время отстает на полсекунды.
Мои извинения за то что меня не совсем правильно поняли. Я хотел табличку из времени из запроса TIME, времени из запроса START и, если запрос отправки на работу посылается сразу же, то jobStartTime ответа. По ним проводится линейная регрессия и вычисляются коэффициенты перед временами TIME и START, которые у меня -1 и 2. А по табличке выше я не смогу ничего сказать...
Цитата:
Сообщение от mike4kz
падает ниже определенного количества, я начинаю сеять нужные семена
У меня это в самом сеятеле сделано, а сколько каких семян надо иметь на складе минимум, пишется в конфиге словариком. При этом неудобно получается каждый раз лазить в склад перепроверять сколько чего есть, поэтому хотелось сделать посадку и сбор урожая в одном экторе. Но сайчас, когда склад быстрый, проблемы нету
Цитата:
Сообщение от mike4kz
По-поводу нового глобального параметра "time" (за пределами event)... Я не думаю, что ваше sig invalig приходит из-за него.
Просто для максимальной похожести на оф.клиент, конечно, не для того, чтобы исправить какие-то баги.
Как мне кажется, sig is not valid приходит, когда мозги сыпятся закопанные или скрафченные, потому что клиент(бот) не отвечает на "эй, вкопай-ка столько зомбиков, у тебя мозги кончились", а должен.
Последний раз редактировалось megabyte0; 21.01.2015 в 19:58.
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Я хотел табличку и
время клиента перед запросом TIME, время в запросе START и jobStartTime?
Добавлено через 9 минут
21.01.2015 18:17:06 Время из TIME: 13721
21.01.2015 18:17:06 Загружаем остров...
21.01.2015 18:17:07 Время из START: 38496
21.01.2015 18:17:07 Время перед отправкой на работы: 38706
21.01.2015 18:17:07 Отправляем зомби на работу: рубить 'Дуб огромный'
21.01.2015 18:17:07 jobStartTime: 39055
21.01.2015 18:17:49 Время из TIME: 15349
21.01.2015 18:17:49 Загружаем остров...
21.01.2015 18:17:50 Время из START: 37050
21.01.2015 18:17:50 Время перед отправкой на работы: 37268
21.01.2015 18:17:50 Отправляем зомби на работу: рубить 'Ель большая'
21.01.2015 18:17:50 jobStartTime: 37567
21.01.2015 18:18:17 Время из TIME: 15097
21.01.2015 18:18:17 Загружаем остров...
21.01.2015 18:18:17 Время из START: 38944
21.01.2015 18:18:17 Время перед отправкой на работы: 39154
21.01.2015 18:18:17 Отправляем зомби на работу: рубить 'Дуб огромный'
21.01.2015 18:18:17 jobStartTime: 39552
21.01.2015 18:18:38 Время из TIME: 15719
21.01.2015 18:18:38 Загружаем остров...
21.01.2015 18:18:38 Время из START: 39903
21.01.2015 18:18:38 Время перед отправкой на работы: 40124
21.01.2015 18:18:38 Отправляем зомби на работу: рубить 'Дуб огромный'
21.01.2015 18:18:38 jobStartTime: 40382
21.01.2015 18:19:00 Время из TIME: 15684
21.01.2015 18:19:00 Загружаем остров...
21.01.2015 18:19:00 Время из START: 39728
21.01.2015 18:19:01 Время перед отправкой на работы: 39971
21.01.2015 18:19:01 Отправляем зомби на работу: рубить 'Ель большая'
21.01.2015 18:19:01 jobStartTime: 40374
21.01.2015 18:19:18 Время из TIME: 14492
21.01.2015 18:19:18 Загружаем остров...
21.01.2015 18:19:19 Время из START: 39658
21.01.2015 18:19:19 Время перед отправкой на работы: 39863
21.01.2015 18:19:19 Отправляем зомби на работу: рубить 'Ель маленькая'
21.01.2015 18:19:19 jobStartTime: 40167
21.01.2015 18:19:40 Время из TIME: 15360
21.01.2015 18:19:40 Загружаем остров...
21.01.2015 18:19:40 Время из START: 39571
21.01.2015 18:19:41 Время перед отправкой на работы: 39772
21.01.2015 18:19:41 Отправляем зомби на работу: рубить 'Дуб огромный'
21.01.2015 18:19:41 jobStartTime: 40064
21.01.2015 18:20:02 Время из TIME: 16679
21.01.2015 18:20:02 Загружаем остров...
21.01.2015 18:20:02 Время из START: 35438
21.01.2015 18:20:02 Время перед отправкой на работы: 35644
21.01.2015 18:20:02 Отправляем зомби на работу: рубить 'Ель большая'
21.01.2015 18:20:02 jobStartTime: 35914
21.01.2015 18:20:14 Время из TIME: 14580
21.01.2015 18:20:14 Загружаем остров...
21.01.2015 18:20:14 Время из START: 31105
21.01.2015 18:20:15 Время перед отправкой на работы: 31312
21.01.2015 18:20:15 Отправляем зомби на работу: рубить 'Ель маленькая'
21.01.2015 18:20:15 jobStartTime: 31847
Последний раз редактировалось ruslanische; 21.01.2015 в 19:20.
Причина: Добавлено сообщение
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Всё совершенно непредсказуемо "ходит ходуном"
Может из-за того, что у меня:
перед TIME
self.gameTimer.add_time_shift_range(12000, 15000)
перед START
self.gameTimer.add_time_shift_range(15000, 25000)
Пока выход вижу в том, чтобы после получения jobEndTime ждать пару секунд и проверять, закончилась ли работа.
А перед собиранием грядок - наборот.
Добавлено через 2 минуты
Цитата:
Можете подальше друг от друга расставить разные значения TIME и/или START
это как?
Последний раз редактировалось ruslanische; 21.01.2015 в 21:26.
Причина: Добавлено сообщение
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Сообщение от ruslanische
это как?
Побольше раскидать разницу в _range( ? Вообще я думал про 3 значения с шагом секунд 10 по одной из переменных, и 3 значения с другим шагом, секунды 3, по другой, 3*3 = 9 оттуда и взято.
Типа того:
TIME|START
1000|3000
1000|6000
1000|9000
11000|13000
11000|16000
11000|19000
21000|23000
21000|26000
21000|29000
, но необязательно так и необязательно настолько точно. Просто у меня же был jobStartTime совсем далеко от времени START ? Я поэтому и не совсем верю, что всё так просто (добавить ко времени START пару секунд), пока не будет доказательств... Оттуда и проблема...
Re: Zombot (Клиент для игры Зомби ферма) [Обсуждение]
Цитата:
Сообщение от ruslanische
тем более мой декомпиллятор выдает абракадабру типа -:< вместо методов.
Она там и есть, но не во всех методах. Я пользую ffdec , он free, там есть "заменить все нехорошие названия методов/классов", потом по коду понимать приходится.
Да флеш на C++ похож, сильно. Уж не думаю, что у Вас бы там проблемы были.