Working harvesting detection, working manufacturing detection.
@@ -1,46 +0,0 @@
|
||||
{"time": "21:22:12", "frame": 0, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 204, "action_points": 140}}
|
||||
{"time": "21:22:13", "frame": 1, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 204, "action_points": 140}}
|
||||
{"time": "21:22:14", "frame": 2, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 204, "action_points": 140}}
|
||||
{"time": "21:22:16", "frame": 3, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 203, "action_points": 140}}
|
||||
{"time": "21:22:17", "frame": 4, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 202, "action_points": 140}}
|
||||
{"time": "21:22:18", "frame": 5, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 202, "action_points": 140}}
|
||||
{"time": "21:22:20", "frame": 6, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 201, "action_points": 140}}
|
||||
{"time": "21:22:21", "frame": 7, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 201, "action_points": 140}}
|
||||
{"time": "21:22:22", "frame": 8, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 200, "action_points": 140}}
|
||||
{"time": "21:22:23", "frame": 9, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 200, "action_points": 140}}
|
||||
{"time": "21:22:24", "frame": 10, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 200, "action_points": 140}}
|
||||
{"time": "21:22:26", "frame": 11, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 199, "action_points": 140}}
|
||||
{"time": "21:22:27", "frame": 12, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 198, "action_points": 140}}
|
||||
{"time": "21:22:28", "frame": 13, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 198, "action_points": 140}}
|
||||
{"time": "21:22:29", "frame": 14, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1971, "action_points": 140}}
|
||||
{"time": "21:22:30", "frame": 15, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1971, "action_points": 140}}
|
||||
{"time": "21:22:32", "frame": 16, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 196, "action_points": 140}}
|
||||
{"time": "21:22:33", "frame": 17, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 196, "action_points": 140}}
|
||||
{"time": "21:22:34", "frame": 18, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 196, "action_points": 140}}
|
||||
{"time": "21:22:36", "frame": 19, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 195, "action_points": 140}}
|
||||
{"time": "21:22:37", "frame": 20, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 194, "action_points": 140}}
|
||||
{"time": "21:22:38", "frame": 21, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 194, "action_points": 140}}
|
||||
{"time": "21:22:40", "frame": 22, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 193, "action_points": 140}}
|
||||
{"time": "21:22:41", "frame": 23, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 193, "action_points": 140}}
|
||||
{"time": "21:22:43", "frame": 24, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1921, "action_points": 140}}
|
||||
{"time": "21:22:44", "frame": 25, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1921, "action_points": 140}}
|
||||
{"time": "21:22:46", "frame": 26, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1911, "action_points": 140}}
|
||||
{"time": "21:22:47", "frame": 27, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 190, "action_points": 140}}
|
||||
{"time": "21:22:49", "frame": 28, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 190, "action_points": 140}}
|
||||
{"time": "21:22:50", "frame": 29, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 189, "action_points": 140}}
|
||||
{"time": "21:22:52", "frame": 30, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 189, "action_points": 140}}
|
||||
{"time": "21:22:53", "frame": 31, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 188, "action_points": 140}}
|
||||
{"time": "21:22:55", "frame": 32, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 188, "action_points": 140}}
|
||||
{"time": "21:22:56", "frame": 33, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1871, "action_points": 140}}
|
||||
{"time": "21:22:58", "frame": 34, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 1871, "action_points": 140}}
|
||||
{"time": "21:22:59", "frame": 35, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 186, "action_points": 140}}
|
||||
{"time": "21:23:01", "frame": 36, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:02", "frame": 37, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:04", "frame": 38, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:05", "frame": 39, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:07", "frame": 40, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:08", "frame": 41, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:10", "frame": 42, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:12", "frame": 43, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 184, "action_points": 140}}
|
||||
{"time": "21:23:13", "frame": 44, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 184, "action_points": 140}}
|
||||
{"time": "21:23:15", "frame": 45, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 183, "action_points": 140}}
|
||||
@@ -1,127 +0,0 @@
|
||||
"""Lightweight image analysis for live capture feedback.
|
||||
|
||||
All stats are computed on a small downsampled copy so the demo loop stays
|
||||
fast at full 1080p capture.
|
||||
"""
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from PIL import Image
|
||||
|
||||
ANALYSIS_SIZE = (320, 180)
|
||||
_TOP_COLORS = 5
|
||||
|
||||
# Harvesting is detected from the single pixel at this window coordinate.
|
||||
# Harvesting shows a light tan (~#f2c285, luma ~201); idle shows dark brown
|
||||
# (~#664c33, luma ~81).
|
||||
HARVEST_PIXEL = (1710, 1068)
|
||||
HARVEST_LUMA_THRESHOLD = 140
|
||||
|
||||
# The small numeric readout just before each bar at the bottom of the panel.
|
||||
# Regions are (x0, y0, x1, y1) in captured-window pixel coordinates. y0 starts
|
||||
# below the panel's top border line (y=1032) which would confuse OCR.
|
||||
NUMBER_REGIONS = {
|
||||
"mana": (0, 1033, 62, 1048),
|
||||
"food": (195, 1033, 226, 1048),
|
||||
"health": (355, 1033, 389, 1048),
|
||||
"inventory": (520, 1033, 552, 1048),
|
||||
"action_points": (610, 1033, 714, 1048),
|
||||
}
|
||||
|
||||
_OCR_SCALE = 3
|
||||
|
||||
try:
|
||||
import pytesseract
|
||||
except ImportError:
|
||||
pytesseract = None
|
||||
|
||||
|
||||
def _luma(pixel):
|
||||
r, g, b = pixel
|
||||
return round(0.299 * r + 0.587 * g + 0.114 * b)
|
||||
|
||||
|
||||
def load_image(path):
|
||||
"""Load a PNG as RGB; return None if the file is unreadable/corrupt."""
|
||||
try:
|
||||
return Image.open(path).convert("RGB")
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def is_harvesting(image):
|
||||
"""Return whether the player is harvesting, based on the harvest pixel."""
|
||||
rgb = image.convert("RGB")
|
||||
color = rgb.getpixel(HARVEST_PIXEL)
|
||||
lum = _luma(color)
|
||||
return {
|
||||
"is_harvesting": lum >= HARVEST_LUMA_THRESHOLD,
|
||||
"color": "#%02x%02x%02x" % color,
|
||||
"luma": lum,
|
||||
}
|
||||
|
||||
|
||||
def _ocr_number(image, region):
|
||||
"""OCR a single number region; return int or None if it can't be read."""
|
||||
if pytesseract is None:
|
||||
return None
|
||||
crop = image.convert("L").crop(region)
|
||||
crop = crop.resize((crop.width * _OCR_SCALE, crop.height * _OCR_SCALE))
|
||||
crop = crop.point(lambda p: 0 if p < 140 else 255)
|
||||
try:
|
||||
text = pytesseract.image_to_string(
|
||||
crop,
|
||||
config="--psm 7 -c tessedit_char_whitelist=0123456789",
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
digits = "".join(ch for ch in text if ch.isdigit())
|
||||
return int(digits) if digits else None
|
||||
|
||||
|
||||
def read_bar_values(image):
|
||||
"""Return the numeric readout before each bar, or None if unreadable."""
|
||||
rgb = image.convert("RGB")
|
||||
return {
|
||||
name: _ocr_number(rgb, region)
|
||||
for name, region in NUMBER_REGIONS.items()
|
||||
}
|
||||
|
||||
|
||||
def _pixels(image):
|
||||
px = image.load()
|
||||
w, h = image.size
|
||||
return [px[x, y] for y in range(h) for x in range(w)]
|
||||
|
||||
|
||||
def summarize(image):
|
||||
"""Return size, mean RGB, and dominant colors of a captured frame."""
|
||||
small = image.convert("RGB").resize(ANALYSIS_SIZE)
|
||||
pixels = _pixels(small)
|
||||
n = len(pixels)
|
||||
total = [0, 0, 0]
|
||||
for r, g, b in pixels:
|
||||
total[0] += r
|
||||
total[1] += g
|
||||
total[2] += b
|
||||
mean = tuple(round(c / n) for c in total)
|
||||
top = Counter(pixels).most_common(_TOP_COLORS)
|
||||
dominant = [
|
||||
{"hex": "#%02x%02x%02x" % color, "fraction": round(count / n, 3)}
|
||||
for color, count in top
|
||||
]
|
||||
return {"size": (image.width, image.height), "mean_rgb": mean, "dominant": dominant}
|
||||
|
||||
|
||||
def difference(a, b):
|
||||
"""Sum of absolute channel differences between two same-size RGB images."""
|
||||
if a.size != b.size:
|
||||
return None
|
||||
pa, pb = a.load(), b.load()
|
||||
w, h = a.size
|
||||
diff = 0
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
ca, cb = pa[x, y], pb[x, y]
|
||||
diff += abs(ca[0] - cb[0]) + abs(ca[1] - cb[1]) + abs(ca[2] - cb[2])
|
||||
return diff
|
||||
@@ -0,0 +1,144 @@
|
||||
# AGENTS.md
|
||||
|
||||
Guidance for AI agents working in this repo. Read before editing. The goal of
|
||||
these rules is to keep the code clean, consistent, and easy to extend without
|
||||
breaking the JSONL log contract or the test suite.
|
||||
|
||||
## Project in one line
|
||||
|
||||
Capture the Eternal Lands X11 window, detect harvesting + the 5 HUD bar
|
||||
numbers, track the draggable Manufacturing/Inventory windows, OCR the craft
|
||||
status message, and append one JSON object per frame to a JSONL log for a
|
||||
future "observer" to consume.
|
||||
|
||||
## Commands
|
||||
|
||||
Run everything through the project virtualenv — the system Python is
|
||||
externally managed (PEP 668) and lacks the deps:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate # then plain python/pytest work
|
||||
# or, without activating:
|
||||
.venv/bin/python -m pytest tests/ -v
|
||||
.venv/bin/python main.py
|
||||
.venv/bin/python main.py --replay tests/images/harvesting
|
||||
```
|
||||
|
||||
- Always run the full test suite before finishing: `.venv/bin/python -m pytest tests/`
|
||||
- If you add a Python dependency, add it to `requirements.txt` and install it
|
||||
into `.venv`.
|
||||
|
||||
## Architecture and responsibilities
|
||||
|
||||
- `capture.py` — the ONLY module that touches X11. Returns PIL RGBA images.
|
||||
- `analyze.py` — pure functions only (no I/O, no side effects). Every function
|
||||
takes a PIL image (or path) and returns data. Includes window detection
|
||||
(`verify_window`, `find_windows`), craft-message OCR
|
||||
(`read_manufacturing_message`), and `classify_message`.
|
||||
- `main.py` — CLI, capture loop, replay mode, JSON record construction, and
|
||||
logging. Owns all file/stdout I/O. Also owns the window-position cache
|
||||
(`WindowTracker`): verify the cached position each frame, rescan the full
|
||||
frame only when a window moves.
|
||||
- `tests/` — folder-driven pytest tests plus fixture images.
|
||||
|
||||
Keep this split. New analysis logic goes in `analyze.py`. New ways to run the
|
||||
loop go in `main.py`. Never put capture logic in `analyze.py` or analysis in
|
||||
`capture.py`.
|
||||
|
||||
## Code style
|
||||
|
||||
- **No comments unless they explain WHY** (non-obvious decisions, measured
|
||||
constants). Docstrings on public functions and modules are expected. Match
|
||||
the existing tone: short, declarative docstrings; a brief comment only where
|
||||
the reasoning is not obvious (e.g. "y0 starts at 1033 because row 1032 is
|
||||
the panel border, which confuses OCR").
|
||||
- Standard library + the deps in `requirements.txt` only. Do not introduce
|
||||
new heavy dependencies without a strong reason and a requirements.txt
|
||||
update.
|
||||
- Return plain dicts/lists of primitives (JSON-serializable) from `analyze.py`
|
||||
functions — never objects or PIL types — so results drop straight into the
|
||||
log.
|
||||
- Function names are snake_case and descriptive (`is_harvesting`,
|
||||
`read_bar_values`, `load_image`).
|
||||
- Module-level constants are UPPER_SNAKE_CASE; private helpers/constants are
|
||||
`_leading_underscore`.
|
||||
- No type hints required, but if you add them keep them consistent across a
|
||||
file.
|
||||
|
||||
## Screen-coordinate calibration
|
||||
|
||||
All pixel coordinates are measured values for a **1920x1080** window and live
|
||||
as constants at the top of `analyze.py` (`HARVEST_PIXEL`,
|
||||
`HARVEST_LUMA_THRESHOLD`, `NUMBER_REGIONS`).
|
||||
|
||||
Window coordinates are **relative to the window title bar**, never fixed
|
||||
screen positions — the player can drag the Manufacturing/Inventory windows.
|
||||
Calibrated constants: `MESSAGE_SEARCH` (the craft message sits at
|
||||
`(title_x - 161, title_y + 121)`, verified at 5 different window positions)
|
||||
and the `TITLE_VERIFY_CROP`/`FIND_TITLE_OFFSETS` tuning.
|
||||
|
||||
- When you change a coordinate or threshold, you MUST re-run the tests. If you
|
||||
have a screenshot proving the new value, drop it in the right
|
||||
`tests/images/` folder first so the tests verify your change.
|
||||
- Never "adjust" a coordinate in a test to make it pass. Verify against the
|
||||
actual images.
|
||||
- If the game window resolution changes, the calibration must be redone from
|
||||
real screenshots — do not guess coordinates.
|
||||
|
||||
## Tests
|
||||
|
||||
- Tests are **folder-driven**:
|
||||
- `tests/images/harvesting/*.png` → `is_harvesting` must be True
|
||||
- `tests/images/not_harvesting/*.png` → must be False
|
||||
- `tests/images/corrupt/*.png` → `load_image` must return None, no crash
|
||||
- `tests/images/manufacturing/<category>/*.png` → the craft message read at
|
||||
the pinned title position must classify to `<category>`
|
||||
- `tests/images/inventory/*.png` → `verify_window` at the pinned position
|
||||
- Adding an edge case = dropping a PNG into the right folder. Do that instead
|
||||
of writing one-off test code, unless a new *expected value* must be pinned
|
||||
(then update `tests/test_bars.py`, e.g. `INVENTORY_EXPECTED`, or
|
||||
`tests/test_windows.py`, e.g. `MFG_POSITION`).
|
||||
- Keep fixture images small in number and real (actual captured frames), not
|
||||
synthetic.
|
||||
- Pinned OCR values in `test_bars.py` are ground truth from real frames. If a
|
||||
frame's expected value must change, verify the number by reading the image
|
||||
— never just update the test to match a new wrong OCR result.
|
||||
|
||||
## The JSONL log contract
|
||||
|
||||
`main.py` appends one JSON object per line to `analysis.jsonl` (default).
|
||||
Shape:
|
||||
|
||||
```json
|
||||
{"time": "…", "frame": 4, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 39, "health": 40, "inventory": 106, "action_points": 140}, "manufacturing_window": [1104, 327], "manufacturing_message": {"text": "you successfully created 1 potion of minor healing", "color": "green", "category": "success"}, "inventory_window": [1629, 686]}
|
||||
```
|
||||
|
||||
- The future observer parses this log. **Do not change field names, types, or
|
||||
add/remove fields casually.** If you must extend it, add new keys (never
|
||||
repurpose existing ones) and update `design.md`.
|
||||
- `bars.*` may be `null` when OCR fails — consumers must tolerate that; keep
|
||||
it that way.
|
||||
- `manufacturing_window` / `inventory_window` are `[x, y]` title-bar
|
||||
coordinates, or `null` when the window is not currently verified.
|
||||
`manufacturing_message` is `{text, color, category}` (`text`/`color` are
|
||||
`null` when no message shows), or `null` when the window position is
|
||||
unverified. Message categories: `working`, `success`, `stopped`, `failed`,
|
||||
`failed_lost`, `hungry`, `overloaded`, `none`, `unknown`. Consumers must
|
||||
tolerate all `null` cases.
|
||||
- Keep the log append-only and line-per-record (JSONL). It is tail-friendly.
|
||||
|
||||
## Error handling
|
||||
|
||||
- Analysis functions must never crash the capture loop. `analyze.py` catches
|
||||
per-item failures (e.g. OCR returns `None` on error) and returns `None`
|
||||
rather than raising.
|
||||
- Optional dependency pattern: `pytesseract` is imported inside
|
||||
`try/except ImportError` and the module degrades gracefully
|
||||
(`pytesseract = None`). Follow this pattern for any new optional dep.
|
||||
- Corrupt images → `load_image` returns `None`; callers skip them.
|
||||
|
||||
## Docs
|
||||
|
||||
- `design.md` — the source of truth for capabilities, calibration,
|
||||
architecture, and the log format. Keep it in sync when you change behavior.
|
||||
- `AGENTS.md` — these rules. Update it if a convention changes.
|
||||
@@ -0,0 +1,256 @@
|
||||
{"time": "21:22:12", "frame": 0, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 204, "action_points": 140}}
|
||||
{"time": "21:22:13", "frame": 1, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 204, "action_points": 140}}
|
||||
{"time": "21:22:14", "frame": 2, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 204, "action_points": 140}}
|
||||
{"time": "21:22:16", "frame": 3, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 203, "action_points": 140}}
|
||||
{"time": "21:22:17", "frame": 4, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 202, "action_points": 140}}
|
||||
{"time": "21:22:18", "frame": 5, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 202, "action_points": 140}}
|
||||
{"time": "21:22:20", "frame": 6, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 201, "action_points": 140}}
|
||||
{"time": "21:22:21", "frame": 7, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 201, "action_points": 140}}
|
||||
{"time": "21:22:22", "frame": 8, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 200, "action_points": 140}}
|
||||
{"time": "21:22:23", "frame": 9, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 200, "action_points": 140}}
|
||||
{"time": "21:22:24", "frame": 10, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 200, "action_points": 140}}
|
||||
{"time": "21:22:26", "frame": 11, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 199, "action_points": 140}}
|
||||
{"time": "21:22:27", "frame": 12, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 198, "action_points": 140}}
|
||||
{"time": "21:22:28", "frame": 13, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 198, "action_points": 140}}
|
||||
{"time": "21:22:29", "frame": 14, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1971, "action_points": 140}}
|
||||
{"time": "21:22:30", "frame": 15, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1971, "action_points": 140}}
|
||||
{"time": "21:22:32", "frame": 16, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 196, "action_points": 140}}
|
||||
{"time": "21:22:33", "frame": 17, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 196, "action_points": 140}}
|
||||
{"time": "21:22:34", "frame": 18, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 196, "action_points": 140}}
|
||||
{"time": "21:22:36", "frame": 19, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 195, "action_points": 140}}
|
||||
{"time": "21:22:37", "frame": 20, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 194, "action_points": 140}}
|
||||
{"time": "21:22:38", "frame": 21, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 194, "action_points": 140}}
|
||||
{"time": "21:22:40", "frame": 22, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 193, "action_points": 140}}
|
||||
{"time": "21:22:41", "frame": 23, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 193, "action_points": 140}}
|
||||
{"time": "21:22:43", "frame": 24, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1921, "action_points": 140}}
|
||||
{"time": "21:22:44", "frame": 25, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1921, "action_points": 140}}
|
||||
{"time": "21:22:46", "frame": 26, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1911, "action_points": 140}}
|
||||
{"time": "21:22:47", "frame": 27, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 190, "action_points": 140}}
|
||||
{"time": "21:22:49", "frame": 28, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 190, "action_points": 140}}
|
||||
{"time": "21:22:50", "frame": 29, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 189, "action_points": 140}}
|
||||
{"time": "21:22:52", "frame": 30, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 189, "action_points": 140}}
|
||||
{"time": "21:22:53", "frame": 31, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 188, "action_points": 140}}
|
||||
{"time": "21:22:55", "frame": 32, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 188, "action_points": 140}}
|
||||
{"time": "21:22:56", "frame": 33, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 28, "health": 40, "inventory": 1871, "action_points": 140}}
|
||||
{"time": "21:22:58", "frame": 34, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 1871, "action_points": 140}}
|
||||
{"time": "21:22:59", "frame": 35, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 186, "action_points": 140}}
|
||||
{"time": "21:23:01", "frame": 36, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:02", "frame": 37, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:04", "frame": 38, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:05", "frame": 39, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:07", "frame": 40, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:08", "frame": 41, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:10", "frame": 42, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 185, "action_points": 140}}
|
||||
{"time": "21:23:12", "frame": 43, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 184, "action_points": 140}}
|
||||
{"time": "21:23:13", "frame": 44, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 184, "action_points": 140}}
|
||||
{"time": "21:23:15", "frame": 45, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": null, "health": 40, "inventory": 183, "action_points": 140}}
|
||||
{"time": "21:33:25", "frame": 0, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": null, "action_points": 140}}
|
||||
{"time": "21:33:27", "frame": 1, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": null, "action_points": 140}}
|
||||
{"time": "21:33:29", "frame": 2, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": null, "action_points": 140}}
|
||||
{"time": "21:33:32", "frame": 3, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 40, "inventory": null, "action_points": 140}}
|
||||
{"time": "21:33:34", "frame": 4, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 26, "health": 40, "inventory": 1, "action_points": 140}}
|
||||
{"time": "21:33:36", "frame": 5, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 26, "health": 40, "inventory": 1, "action_points": 140}}
|
||||
{"time": "21:33:39", "frame": 6, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 24, "health": 40, "inventory": 9, "action_points": 140}}
|
||||
{"time": "21:33:42", "frame": 7, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 22, "health": 40, "inventory": 18, "action_points": 140}}
|
||||
{"time": "21:33:45", "frame": 8, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 22, "health": 40, "inventory": 18, "action_points": 140}}
|
||||
{"time": "21:33:48", "frame": 9, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 22, "health": 40, "inventory": 18, "action_points": 140}}
|
||||
{"time": "21:33:51", "frame": 10, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 2, "health": 40, "inventory": 22, "action_points": 140}}
|
||||
{"time": "21:33:54", "frame": 11, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 18, "health": 40, "inventory": 30, "action_points": 140}}
|
||||
{"time": "21:33:58", "frame": 12, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 18, "health": 40, "inventory": 30, "action_points": 140}}
|
||||
{"time": "21:34:01", "frame": 13, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 17, "health": 40, "inventory": 30, "action_points": 140}}
|
||||
{"time": "21:34:04", "frame": 14, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 15, "health": 40, "inventory": 34, "action_points": 140}}
|
||||
{"time": "21:34:07", "frame": 15, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 12, "health": 40, "inventory": 38, "action_points": 140}}
|
||||
{"time": "21:34:11", "frame": 16, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 10, "health": 40, "inventory": 42, "action_points": 140}}
|
||||
{"time": "12:59:47", "frame": 0, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 33, "health": 40, "inventory": 57, "action_points": 140}}
|
||||
{"time": "12:59:49", "frame": 1, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 33, "health": 40, "inventory": 57, "action_points": 140}}
|
||||
{"time": "12:59:51", "frame": 2, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 33, "health": 40, "inventory": 57, "action_points": 140}}
|
||||
{"time": "12:59:53", "frame": 3, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 32, "health": 40, "inventory": 60, "action_points": 140}}
|
||||
{"time": "12:59:55", "frame": 4, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 29, "health": 40, "inventory": 69, "action_points": 140}}
|
||||
{"time": "12:59:57", "frame": 5, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 28, "health": 40, "inventory": 69, "action_points": 140}}
|
||||
{"time": "12:59:59", "frame": 6, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 26, "health": 40, "inventory": 75, "action_points": 140}}
|
||||
{"time": "13:00:02", "frame": 7, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 25, "health": 40, "inventory": 75, "action_points": 140}}
|
||||
{"time": "13:00:04", "frame": 8, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 25, "health": 40, "inventory": 75, "action_points": 140}}
|
||||
{"time": "13:00:06", "frame": 9, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 25, "health": 40, "inventory": 75, "action_points": 140}}
|
||||
{"time": "13:00:08", "frame": 10, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 24, "health": 40, "inventory": 75, "action_points": 140}}
|
||||
{"time": "13:00:10", "frame": 11, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 24, "health": 40, "inventory": 75, "action_points": 140}}
|
||||
{"time": "13:00:12", "frame": 12, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 23, "health": 40, "inventory": 78, "action_points": 140}}
|
||||
{"time": "13:00:15", "frame": 13, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 2, "health": 40, "inventory": 85, "action_points": 140}}
|
||||
{"time": "13:00:17", "frame": 14, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 2, "health": 40, "inventory": 85, "action_points": 140}}
|
||||
{"time": "13:00:20", "frame": 15, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 2, "health": 40, "inventory": 85, "action_points": 140}}
|
||||
{"time": "13:00:22", "frame": 16, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 28, "food": 2, "health": 40, "inventory": 85, "action_points": 140}}
|
||||
{"time": "13:00:25", "frame": 17, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 19, "health": 40, "inventory": 89, "action_points": 140}}
|
||||
{"time": "13:00:27", "frame": 18, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 18, "health": 40, "inventory": 89, "action_points": 140}}
|
||||
{"time": "13:00:30", "frame": 19, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 18, "health": 40, "inventory": 89, "action_points": 140}}
|
||||
{"time": "13:00:33", "frame": 20, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 96, "action_points": 140}}
|
||||
{"time": "13:00:35", "frame": 21, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 14, "health": 40, "inventory": 1021, "action_points": 140}}
|
||||
{"time": "13:00:38", "frame": 22, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 12, "health": 40, "inventory": 105, "action_points": 140}}
|
||||
{"time": "13:00:40", "frame": 23, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 105, "action_points": 140}}
|
||||
{"time": "13:00:43", "frame": 24, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 10, "health": 40, "inventory": 108, "action_points": 140}}
|
||||
{"time": "13:00:45", "frame": 25, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 7, "health": 40, "inventory": 1171, "action_points": 140}}
|
||||
{"time": "13:00:48", "frame": 26, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 6, "health": 40, "inventory": 1171, "action_points": 140}}
|
||||
{"time": "13:00:50", "frame": 27, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 5, "health": 40, "inventory": 120, "action_points": 140}}
|
||||
{"time": "13:00:53", "frame": 28, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 4, "health": 40, "inventory": 124, "action_points": 140}}
|
||||
{"time": "13:00:55", "frame": 29, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 3, "health": 40, "inventory": 1271, "action_points": 140}}
|
||||
{"time": "13:00:58", "frame": 30, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": 1, "health": 40, "inventory": 133, "action_points": 140}}
|
||||
{"time": "13:01:00", "frame": 31, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 133, "action_points": 140}}
|
||||
{"time": "13:01:03", "frame": 32, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 133, "action_points": 140}}
|
||||
{"time": "13:01:05", "frame": 33, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 133, "action_points": 140}}
|
||||
{"time": "13:01:08", "frame": 34, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 133, "action_points": 140}}
|
||||
{"time": "13:01:10", "frame": 35, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 133, "action_points": 140}}
|
||||
{"time": "13:01:12", "frame": 36, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 29, "food": null, "health": 40, "inventory": 133, "action_points": 140}}
|
||||
{"time": "13:48:18", "frame": 0, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 207, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:19", "frame": 1, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 205, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:20", "frame": 2, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 204, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:21", "frame": 3, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 204, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:22", "frame": 4, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 203, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:23", "frame": 5, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 203, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:24", "frame": 6, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 202, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:25", "frame": 7, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 202, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:27", "frame": 8, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 201, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:28", "frame": 9, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 201, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:29", "frame": 10, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 200, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:30", "frame": 11, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 200, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:31", "frame": 12, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 199, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:32", "frame": 13, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 199, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:33", "frame": 14, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 198, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:34", "frame": 15, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 198, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:35", "frame": 16, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 1971, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:36", "frame": 17, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 1971, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:37", "frame": 18, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 196, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:38", "frame": 19, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 196, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:39", "frame": 20, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 195, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:40", "frame": 21, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 195, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:41", "frame": 22, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 194, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:43", "frame": 23, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 194, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:44", "frame": 24, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 193, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:45", "frame": 25, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 1921, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:48:52", "frame": 26, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 1921, "action_points": 140}, "manufacturing_window": [1104, 337], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:04", "frame": 0, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 186, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:06", "frame": 1, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 183, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:07", "frame": 2, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 182, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:09", "frame": 3, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 1811, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:11", "frame": 4, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 42, "health": 45, "inventory": 180, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:13", "frame": 5, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 179, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:14", "frame": 6, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 178, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:16", "frame": 7, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 1771, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:18", "frame": 8, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 176, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:20", "frame": 9, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 175, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:23", "frame": 10, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 174, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:26", "frame": 11, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 173, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:29", "frame": 12, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 171, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:31", "frame": 13, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 170, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:35", "frame": 14, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 169, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:38", "frame": 15, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 1671, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:40", "frame": 16, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:42", "frame": 17, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 165, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:45", "frame": 18, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 163, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:47", "frame": 19, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 162, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:49", "frame": 20, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 1611, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:52", "frame": 21, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 160, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:55", "frame": 22, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 159, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:49:58", "frame": 23, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 1571, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:00", "frame": 24, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 156, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:06", "frame": 25, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 155, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:08", "frame": 26, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 152, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:10", "frame": 27, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 151, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:12", "frame": 28, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 41, "health": 45, "inventory": 150, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:15", "frame": 29, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 149, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:17", "frame": 30, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1471, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:19", "frame": 31, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 146, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:21", "frame": 32, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 145, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:23", "frame": 33, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 144, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:26", "frame": 34, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 143, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:27", "frame": 35, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1421, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:29", "frame": 36, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:31", "frame": 37, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:36", "frame": 38, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:37", "frame": 39, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:39", "frame": 40, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:41", "frame": 41, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:43", "frame": 42, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:44", "frame": 43, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:46", "frame": 44, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:48", "frame": 45, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you lack the knowledge of padded leather pants to create a leather pants", "color": "red", "category": "unknown"}, "inventory_window": null}
|
||||
{"time": "13:50:49", "frame": 46, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 1411, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:50:51", "frame": 47, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 38, "health": 45, "inventory": 149, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you successfully created 1 thread", "color": "green", "category": "success"}, "inventory_window": null}
|
||||
{"time": "13:50:53", "frame": 48, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 36, "health": 45, "inventory": 153, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:50:55", "frame": 49, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 36, "health": 45, "inventory": 153, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:50:57", "frame": 50, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 36, "health": 45, "inventory": 153, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:50:59", "frame": 51, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 35, "health": 45, "inventory": 158, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread, and lost the ingredients", "color": "red", "category": "failed_lost"}, "inventory_window": null}
|
||||
{"time": "13:51:01", "frame": 52, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 35, "health": 45, "inventory": 158, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread, and lost the ingredients", "color": "red", "category": "failed_lost"}, "inventory_window": null}
|
||||
{"time": "13:51:03", "frame": 53, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 35, "health": 45, "inventory": 158, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread, and lost the ingredients", "color": "red", "category": "failed_lost"}, "inventory_window": null}
|
||||
{"time": "13:51:07", "frame": 54, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 35, "health": 45, "inventory": 158, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread, and lost the ingredients", "color": "red", "category": "failed_lost"}, "inventory_window": null}
|
||||
{"time": "13:51:09", "frame": 55, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 34, "health": 45, "inventory": 158, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:10", "frame": 56, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 34, "health": 45, "inventory": 158, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:51:12", "frame": 57, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 33, "health": 45, "inventory": 162, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you successfully created 1 thread", "color": "green", "category": "success"}, "inventory_window": null}
|
||||
{"time": "13:51:13", "frame": 58, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 30, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:15", "frame": 59, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 29, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:17", "frame": 60, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 29, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:51:19", "frame": 61, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 28, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 45], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:24", "frame": 62, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 28, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:26", "frame": 63, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 28, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:28", "frame": 64, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 28, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:29", "frame": 65, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 28, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:31", "frame": 66, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 28, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:33", "frame": 67, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 28, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:51:37", "frame": 68, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:39", "frame": 69, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 45, "inventory": 166, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:51:41", "frame": 70, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 25, "health": 45, "inventory": 170, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:42", "frame": 71, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 171, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:44", "frame": 72, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 40, "health": 45, "inventory": 171, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:51:46", "frame": 73, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 39, "health": 45, "inventory": 171, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:47", "frame": 74, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 39, "health": 45, "inventory": 171, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:51:49", "frame": 75, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 37, "health": 45, "inventory": 179, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you successfully created 1 thread", "color": "green", "category": "success"}, "inventory_window": null}
|
||||
{"time": "13:51:51", "frame": 76, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 36, "health": 45, "inventory": 179, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:51:53", "frame": 77, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 35, "health": 45, "inventory": 184, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread, and lost the ingredients", "color": "red", "category": "failed_lost"}, "inventory_window": null}
|
||||
{"time": "13:51:55", "frame": 78, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 34, "health": 45, "inventory": 184, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:51:57", "frame": 79, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 33, "health": 45, "inventory": 188, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you successfully created 1 thread", "color": "green", "category": "success"}, "inventory_window": null}
|
||||
{"time": "13:51:59", "frame": 80, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 32, "health": 45, "inventory": 1921, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you successfully created 1 thread", "color": "green", "category": "success"}, "inventory_window": null}
|
||||
{"time": "13:52:01", "frame": 81, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 30, "health": 45, "inventory": 201, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread, and lost the ingredients", "color": "red", "category": "failed_lost"}, "inventory_window": null}
|
||||
{"time": "13:52:02", "frame": 82, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 30, "health": 45, "inventory": 201, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:52:15", "frame": 83, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 29, "health": 45, "inventory": 205, "action_points": 140}, "manufacturing_window": null, "manufacturing_message": null, "inventory_window": null}
|
||||
{"time": "13:52:17", "frame": 84, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 45, "inventory": 210, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread, and lost the ingredients", "color": "red", "category": "failed_lost"}, "inventory_window": null}
|
||||
{"time": "13:52:18", "frame": 85, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": null, "health": 45, "inventory": 210, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:52:20", "frame": 86, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 25, "health": 45, "inventory": 214, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:52:22", "frame": 87, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 25, "health": 45, "inventory": 214, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:52:23", "frame": 88, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 24, "health": 45, "inventory": 214, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:52:25", "frame": 89, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 23, "health": 45, "inventory": 214, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you started working...", "color": "green", "category": "working"}, "inventory_window": null}
|
||||
{"time": "13:52:26", "frame": 90, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 22, "health": 45, "inventory": 214, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:52:28", "frame": 91, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 2, "health": 45, "inventory": 218, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you successfully created 1 thread", "color": "green", "category": "success"}, "inventory_window": null}
|
||||
{"time": "13:52:30", "frame": 92, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 20, "health": 45, "inventory": 218, "action_points": 140}, "manufacturing_window": [201, 49], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:52:34", "frame": 93, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 20, "health": 45, "inventory": 218, "action_points": 140}, "manufacturing_window": [220, 111], "manufacturing_message": {"text": "you failed to create a[n] thread", "color": "red", "category": "failed"}, "inventory_window": null}
|
||||
{"time": "13:52:36", "frame": 94, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 17, "health": 45, "inventory": 230, "action_points": 140}, "manufacturing_window": [220, 111], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:38", "frame": 95, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 17, "health": 45, "inventory": 230, "action_points": 140}, "manufacturing_window": [220, 111], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:39", "frame": 96, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 17, "health": 45, "inventory": 230, "action_points": 140}, "manufacturing_window": [220, 111], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:44", "frame": 97, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 17, "health": 45, "inventory": 230, "action_points": 140}, "manufacturing_window": [220, 111], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:46", "frame": 98, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 45, "inventory": 230, "action_points": 140}, "manufacturing_window": [220, 111], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:51", "frame": 99, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 45, "inventory": 229, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:53", "frame": 100, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 228, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:56", "frame": 101, "is_harvesting": false, "harvest_pixel": "#664c33", "harvest_luma": 81, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 228, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:52:58", "frame": 102, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 228, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:00", "frame": 103, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": null, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:02", "frame": 104, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 226, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:04", "frame": 105, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 225, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:06", "frame": 106, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 224, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:07", "frame": 107, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 223, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:09", "frame": 108, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 222, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:14", "frame": 109, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 32, "health": 44, "inventory": 22, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:16", "frame": 110, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 219, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:18", "frame": 111, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 218, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:19", "frame": 112, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": null, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:21", "frame": 113, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 216, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:23", "frame": 114, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 215, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:25", "frame": 115, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 215, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:26", "frame": 116, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 214, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:28", "frame": 117, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 213, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:30", "frame": 118, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 22, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:32", "frame": 119, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 2, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:34", "frame": 120, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 210, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:36", "frame": 121, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 209, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:38", "frame": 122, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 208, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:39", "frame": 123, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 207, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:41", "frame": 124, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 206, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:45", "frame": 125, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 206, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:47", "frame": 126, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 203, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:49", "frame": 127, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 203, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
{"time": "13:53:51", "frame": 128, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 311, "health": 45, "inventory": 202, "action_points": 140}, "manufacturing_window": [224, 115], "manufacturing_message": {"text": "you stopped working.", "color": "red", "category": "stopped"}, "inventory_window": null}
|
||||
@@ -0,0 +1,298 @@
|
||||
"""Lightweight image analysis for live capture feedback.
|
||||
|
||||
All stats are computed on a small downsampled copy so the demo loop stays
|
||||
fast at full 1080p capture.
|
||||
"""
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from PIL import Image
|
||||
|
||||
ANALYSIS_SIZE = (320, 180)
|
||||
_TOP_COLORS = 5
|
||||
|
||||
# Harvesting is detected from the single pixel at this window coordinate.
|
||||
# Harvesting shows a light tan (~#f2c285, luma ~201); idle shows dark brown
|
||||
# (~#664c33, luma ~81).
|
||||
HARVEST_PIXEL = (1710, 1068)
|
||||
HARVEST_LUMA_THRESHOLD = 140
|
||||
|
||||
# The small numeric readout just before each bar at the bottom of the panel.
|
||||
# Regions are (x0, y0, x1, y1) in captured-window pixel coordinates. y0 starts
|
||||
# below the panel's top border line (y=1032) which would confuse OCR.
|
||||
NUMBER_REGIONS = {
|
||||
"mana": (0, 1033, 62, 1048),
|
||||
"food": (195, 1033, 226, 1048),
|
||||
"health": (355, 1033, 389, 1048),
|
||||
"inventory": (520, 1033, 552, 1048),
|
||||
"action_points": (610, 1033, 714, 1048),
|
||||
}
|
||||
|
||||
_OCR_SCALE = 3
|
||||
|
||||
# --- Crafting windows -------------------------------------------------------
|
||||
# The Manufacturing and Inventory windows are semi-transparent panels the player
|
||||
# can drag around, so nothing about them is fixed in screen space. Their title
|
||||
# bars are located by OCR and everything else (status message, readouts) is
|
||||
# offset from the title top-left. Calibrated from the frame sets in snapshots/.
|
||||
TITLE_VERIFY_CROP = (14, 12, 150, 42) # (left, top, width, height) rel. to a candidate point
|
||||
TITLE_VERIFY_THRESHOLD = 115
|
||||
TITLE_SUBSTRING = {"manufacturing": "anufact", "inventory": "nven"}
|
||||
|
||||
# The craft status line sits at a fixed offset from the Manufacturing title,
|
||||
# measured at 5 different window positions: always (title_x - 161, title_y + 121).
|
||||
# A wider search region absorbs the few pixels of jitter from title detection.
|
||||
MESSAGE_SEARCH = (175, 105, 600, 70) # (left, top, width, height) rel. to title
|
||||
MESSAGE_COLOR_GREEN = (170, 120, 30) # (min green, min g-r, min g-b) for green text
|
||||
MESSAGE_COLOR_RED = (170, 100, 60) # (min red, min r-g, min r-b) for red text
|
||||
GREEN_OCR_THRESHOLD = 150
|
||||
RED_OCR_THRESHOLD = 140
|
||||
MESSAGE_OCR_PSM = 6
|
||||
|
||||
# Full-frame OCR is used to re-locate a window after it moves. Running it at two
|
||||
# scales recovers some frames that fail at one scale on busy scenes. The word
|
||||
# box OCR returns is usually the title itself (0..4px off) so the candidate
|
||||
# title offsets only nudge that box.
|
||||
FIND_OCR_SCALES = (1.0, 0.75)
|
||||
# The OCR word box usually sits on the title text; -7 covers the Inventory
|
||||
# window where OCR reads the glyph a few px below the verifying title bar.
|
||||
FIND_TITLE_OFFSETS = ((0, 0), (0, 4), (0, -7))
|
||||
|
||||
try:
|
||||
import pytesseract
|
||||
except ImportError:
|
||||
pytesseract = None
|
||||
|
||||
|
||||
def _luma(pixel):
|
||||
r, g, b = pixel
|
||||
return round(0.299 * r + 0.587 * g + 0.114 * b)
|
||||
|
||||
|
||||
def load_image(path):
|
||||
"""Load a PNG as RGB; return None if the file is unreadable/corrupt."""
|
||||
try:
|
||||
return Image.open(path).convert("RGB")
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def is_harvesting(image):
|
||||
"""Return whether the player is harvesting, based on the harvest pixel."""
|
||||
rgb = image.convert("RGB")
|
||||
color = rgb.getpixel(HARVEST_PIXEL)
|
||||
lum = _luma(color)
|
||||
return {
|
||||
"is_harvesting": lum >= HARVEST_LUMA_THRESHOLD,
|
||||
"color": "#%02x%02x%02x" % color,
|
||||
"luma": lum,
|
||||
}
|
||||
|
||||
|
||||
def _ocr_number(image, region):
|
||||
"""OCR a single number region; return int or None if it can't be read."""
|
||||
if pytesseract is None:
|
||||
return None
|
||||
crop = image.convert("L").crop(region)
|
||||
crop = crop.resize((crop.width * _OCR_SCALE, crop.height * _OCR_SCALE))
|
||||
crop = crop.point(lambda p: 0 if p < 140 else 255)
|
||||
try:
|
||||
text = pytesseract.image_to_string(
|
||||
crop,
|
||||
config="--psm 7 -c tessedit_char_whitelist=0123456789",
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
digits = "".join(ch for ch in text if ch.isdigit())
|
||||
return int(digits) if digits else None
|
||||
|
||||
|
||||
def read_bar_values(image):
|
||||
"""Return the numeric readout before each bar, or None if unreadable."""
|
||||
rgb = image.convert("RGB")
|
||||
return {
|
||||
name: _ocr_number(rgb, region)
|
||||
for name, region in NUMBER_REGIONS.items()
|
||||
}
|
||||
|
||||
|
||||
def verify_window(image, x, y, window):
|
||||
"""Return True if a window of the given kind has its title bar at (x, y)."""
|
||||
if pytesseract is None:
|
||||
return False
|
||||
left, top, width, height = TITLE_VERIFY_CROP
|
||||
crop = image.convert("L").crop((x - left, y - top, x - left + width, y - top + height))
|
||||
crop = crop.resize((crop.width * _OCR_SCALE, crop.height * _OCR_SCALE))
|
||||
crop = crop.point(lambda p: 0 if p < TITLE_VERIFY_THRESHOLD else 255)
|
||||
try:
|
||||
text = pytesseract.image_to_string(crop, config="--psm 7")
|
||||
except Exception:
|
||||
return False
|
||||
return TITLE_SUBSTRING[window] in text.lower()
|
||||
|
||||
|
||||
def _color_mask(crop, kind):
|
||||
"""Return a binary mask isolating green or red text pixels in a crop."""
|
||||
px = crop.load()
|
||||
width, height = crop.size
|
||||
if kind == "green":
|
||||
lo, dr, db = MESSAGE_COLOR_GREEN
|
||||
def hit(r, g, b):
|
||||
return g > lo and g - r > dr and g - b > db
|
||||
else:
|
||||
lo, dg, db = MESSAGE_COLOR_RED
|
||||
def hit(r, g, b):
|
||||
return r > lo and r - g > dg and r - b > db
|
||||
mask = Image.new("1", (width, height))
|
||||
mask.putdata([1 if hit(*px[x, y]) else 0 for y in range(height) for x in range(width)])
|
||||
return mask
|
||||
|
||||
|
||||
def _ocr_message_channel(crop, box, kind):
|
||||
"""OCR the green or red channel of a crop; return lowercased text or None."""
|
||||
if pytesseract is None:
|
||||
return None
|
||||
channel = crop.split()[1] if kind == "green" else crop.split()[0]
|
||||
channel = channel.crop(box)
|
||||
threshold = GREEN_OCR_THRESHOLD if kind == "green" else RED_OCR_THRESHOLD
|
||||
channel = channel.point(lambda p: 0 if p < threshold else 255)
|
||||
channel = channel.resize((channel.width * _OCR_SCALE, channel.height * _OCR_SCALE))
|
||||
try:
|
||||
text = pytesseract.image_to_string(channel, config=f"--psm {MESSAGE_OCR_PSM}")
|
||||
except Exception:
|
||||
return None
|
||||
text = " ".join(text.split()).lower()
|
||||
return text or None
|
||||
|
||||
|
||||
def classify_message(text, color):
|
||||
"""Map an OCR'd craft status line to a stable category string."""
|
||||
if not text:
|
||||
return "none"
|
||||
if color == "green":
|
||||
if "started working" in text:
|
||||
return "working"
|
||||
if "successfully created" in text:
|
||||
return "success"
|
||||
if "stopped working" in text:
|
||||
return "stopped"
|
||||
if "lost the ingredients" in text:
|
||||
return "failed_lost"
|
||||
if "failed to create" in text or "failed to craft" in text:
|
||||
return "failed"
|
||||
if "hungry" in text:
|
||||
return "hungry"
|
||||
if "overloaded" in text:
|
||||
return "overloaded"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def read_manufacturing_message(image, title_x, title_y):
|
||||
"""OCR the colored status line below a Manufacturing window title.
|
||||
|
||||
Returns {"text", "color", "category"} or None if OCR is unavailable.
|
||||
"""
|
||||
if pytesseract is None:
|
||||
return None
|
||||
left, top, width, height = MESSAGE_SEARCH
|
||||
region = image.convert("RGB").crop(
|
||||
(title_x - left, title_y + top, title_x - left + width, title_y + top + height)
|
||||
)
|
||||
for kind in ("green", "red"):
|
||||
box = _color_mask(region, kind).getbbox()
|
||||
if box is None:
|
||||
continue
|
||||
pad = 2
|
||||
box = (max(0, box[0] - pad), max(0, box[1] - pad),
|
||||
min(region.width, box[2] + pad), min(region.height, box[3] + pad))
|
||||
text = _ocr_message_channel(region, box, kind)
|
||||
if text:
|
||||
return {"text": text, "color": kind, "category": classify_message(text, kind)}
|
||||
return {"text": None, "color": None, "category": "none"}
|
||||
|
||||
|
||||
def _find_title_words(image):
|
||||
"""Return candidate title word boxes from full-frame OCR.
|
||||
|
||||
Each entry is a {"window": kind, "x": float, "y": float} in full-frame
|
||||
pixel coordinates. Words containing the wrong substring (e.g. "manufacture"
|
||||
inside the hungry status message) are filtered out by the caller's verify.
|
||||
"""
|
||||
if pytesseract is None:
|
||||
return []
|
||||
words = []
|
||||
for scale in FIND_OCR_SCALES:
|
||||
im = image.convert("L")
|
||||
if scale != 1.0:
|
||||
im = im.resize((int(im.width * scale), int(im.height * scale)), Image.BICUBIC)
|
||||
try:
|
||||
data = pytesseract.image_to_data(
|
||||
im, config="--oem 1 --psm 11", output_type=pytesseract.Output.DICT
|
||||
)
|
||||
except Exception:
|
||||
continue
|
||||
for i, word in enumerate(data["text"]):
|
||||
flat = word.lower().strip().replace(" ", "")
|
||||
for kind, needle in TITLE_SUBSTRING.items():
|
||||
if needle in flat:
|
||||
words.append(
|
||||
{"window": kind, "x": data["left"][i] / scale, "y": data["top"][i] / scale}
|
||||
)
|
||||
return words
|
||||
|
||||
|
||||
def find_windows(image):
|
||||
"""Locate Manufacturing and Inventory windows from a full frame scan.
|
||||
|
||||
Returns {"manufacturing": (x, y) or None, "inventory": (x, y) or None}.
|
||||
"""
|
||||
found = {"manufacturing": None, "inventory": None}
|
||||
for word in _find_title_words(image):
|
||||
kind = word["window"]
|
||||
if found[kind] is not None:
|
||||
continue
|
||||
for dx, dy in FIND_TITLE_OFFSETS:
|
||||
x, y = round(word["x"] + dx), round(word["y"] + dy)
|
||||
if verify_window(image, x, y, kind):
|
||||
found[kind] = (x, y)
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
def _pixels(image):
|
||||
px = image.load()
|
||||
w, h = image.size
|
||||
return [px[x, y] for y in range(h) for x in range(w)]
|
||||
|
||||
|
||||
def summarize(image):
|
||||
"""Return size, mean RGB, and dominant colors of a captured frame."""
|
||||
small = image.convert("RGB").resize(ANALYSIS_SIZE)
|
||||
pixels = _pixels(small)
|
||||
n = len(pixels)
|
||||
total = [0, 0, 0]
|
||||
for r, g, b in pixels:
|
||||
total[0] += r
|
||||
total[1] += g
|
||||
total[2] += b
|
||||
mean = tuple(round(c / n) for c in total)
|
||||
top = Counter(pixels).most_common(_TOP_COLORS)
|
||||
dominant = [
|
||||
{"hex": "#%02x%02x%02x" % color, "fraction": round(count / n, 3)}
|
||||
for color, count in top
|
||||
]
|
||||
return {"size": (image.width, image.height), "mean_rgb": mean, "dominant": dominant}
|
||||
|
||||
|
||||
def difference(a, b):
|
||||
"""Sum of absolute channel differences between two same-size RGB images."""
|
||||
if a.size != b.size:
|
||||
return None
|
||||
pa, pb = a.load(), b.load()
|
||||
w, h = a.size
|
||||
diff = 0
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
ca, cb = pa[x, y], pb[x, y]
|
||||
diff += abs(ca[0] - cb[0]) + abs(ca[1] - cb[1]) + abs(ca[2] - cb[2])
|
||||
return diff
|
||||
@@ -0,0 +1,213 @@
|
||||
# Design
|
||||
|
||||
## Purpose
|
||||
|
||||
This project watches a running instance of the game **Eternal Lands** (an X11
|
||||
window) and continuously analyzes screenshots of it. In its current form it
|
||||
detects three kinds of state and records them to a JSONL log:
|
||||
|
||||
1. **Harvesting** — whether the player is currently harvesting (mining,
|
||||
cutting, etc.), detected from the color of a single pixel near the bottom
|
||||
of the screen.
|
||||
2. **HUD values** — the numeric readouts next to the five status bars at the
|
||||
bottom of the screen (mana, food, health, inventory capacity, action
|
||||
points), extracted with OCR.
|
||||
3. **Crafting windows** — the player can drag the Manufacturing and Inventory
|
||||
windows anywhere on screen, so they are located relative to their own title
|
||||
bars (not fixed coordinates). The colored status line inside the
|
||||
Manufacturing window is OCR'd and mapped to a category (`working`,
|
||||
`success`, `failed`, `failed_lost`, `hungry`, `overloaded`, `stopped`,
|
||||
`none`).
|
||||
|
||||
The near-term goal is observation only: the log is produced so a future
|
||||
**observer** component can read it and act — e.g., automatically resume
|
||||
harvesting when the player stops, or react when health/food get low. Keeping
|
||||
the log format stable and machine-parseable is therefore a first-class
|
||||
requirement.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- **Window capture** (`capture.py`) — finds a window by title (substring,
|
||||
case-insensitive) on X11 and grabs its pixels even when unfocused or
|
||||
occluded, via `XGetImage` with a root-window fallback.
|
||||
- **Harvesting detection** (`analyze.is_harvesting`) — samples the pixel at
|
||||
`(1710, 1068)`. Light tan (~`#f2c285`, luma ≈ 201) means harvesting; dark
|
||||
brown (~`#664c33`, luma ≈ 81) means idle. Decision is `luma >= 140`.
|
||||
- **Bar value extraction** (`analyze.read_bar_values`) — crops the small
|
||||
number that precedes each bar, binarizes it, upscales 3x, and runs tesseract
|
||||
with a digits-only whitelist (`--psm 7`).
|
||||
- **Window verification** (`analyze.verify_window`) — checks a `150x42` crop
|
||||
around a candidate point for the title substring `anufact` (Manufacturing)
|
||||
or `nven` (Inventory). This is the per-frame cache check; it passes on all
|
||||
53 reference frames.
|
||||
- **Window recovery** (`analyze.find_windows`) — full-frame OCR scan
|
||||
(`--psm 11`, run at two scales) that collects candidate title words and
|
||||
verifies each. Used only when a cached position stops verifying, i.e. after
|
||||
the window moves.
|
||||
- **Craft message OCR** (`analyze.read_manufacturing_message`) — crops a
|
||||
window-relative region, isolates green or red text pixels, tight-bboxes
|
||||
them, and OCRs the isolated color channel.
|
||||
- **Message classification** (`analyze.classify_message`) — maps an OCR'd
|
||||
message plus its color to a stable category string.
|
||||
- **Window cache** (`main.WindowTracker`) — keeps last-known window positions,
|
||||
re-verifies them each frame, and triggers a full-frame rescan only when a
|
||||
cache miss happens.
|
||||
- **JSONL logging** (`main.py`) — appends one JSON object per frame.
|
||||
- **Replay mode** (`main.py --replay DIR`) — processes existing PNGs instead
|
||||
of a live window, so analysis and OCR can be verified offline.
|
||||
- **Test suite** (`tests/`) — folder-driven pytest tests; dropping a new
|
||||
screenshot into a folder automatically covers it.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
capture.py X11 window capture -> PIL Image (RGBA)
|
||||
analyze.py Pure functions: summarize, is_harvesting, read_bar_values,
|
||||
verify_window, find_windows, read_manufacturing_message,
|
||||
classify_message, load_image, difference. No side effects.
|
||||
main.py CLI entry point. Live capture loop or replay mode; owns the
|
||||
WindowTracker cache, builds the JSON record, appends to log.
|
||||
tests/ Folder-driven pytest tests + test images.
|
||||
```
|
||||
|
||||
- `capture.py` is the only module that talks to X11.
|
||||
- `analyze.py` is pure image analysis — every function takes a PIL image (or
|
||||
path) and returns data. This makes it directly testable.
|
||||
- `main.py` wires them together and owns I/O (capturing, logging, printing).
|
||||
|
||||
## HUD calibration
|
||||
|
||||
All screen coordinates are hardcoded for a **1920x1080 captured window** and
|
||||
live at the top of `analyze.py`. They were measured from real screenshots and
|
||||
must move only if the game window resolution/layout changes.
|
||||
|
||||
| Constant | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `HARVEST_PIXEL` | `(1710, 1068)` | pixel that flips color while harvesting |
|
||||
| `HARVEST_LUMA_THRESHOLD` | `140` | above = harvesting, below = idle |
|
||||
| `NUMBER_REGIONS["mana"]` | `(0, 1033, 62, 1048)` | crop before the blue bar |
|
||||
| `NUMBER_REGIONS["food"]` | `(195, 1033, 226, 1048)` | crop before the yellow bar |
|
||||
| `NUMBER_REGIONS["health"]` | `(355, 1033, 389, 1048)` | crop before the red bar |
|
||||
| `NUMBER_REGIONS["inventory"]` | `(520, 1033, 552, 1048)` | crop before the dark bar |
|
||||
| `NUMBER_REGIONS["action_points"]` | `(610, 1033, 714, 1048)` | crop before the purple bar |
|
||||
|
||||
Regions are `(x0, y0, x1, y1)`. `y0` starts at 1033 because row 1032 is the
|
||||
panel's top border, which would confuse OCR.
|
||||
|
||||
Observed values on the reference screenshots: mana `32`, food `39`, health
|
||||
`40`, action points `140`; inventory capacity varies (`110` → `108` → `106`)
|
||||
as items are collected.
|
||||
|
||||
## Window calibration
|
||||
|
||||
The Manufacturing and Inventory windows are semi-transparent panels the player
|
||||
can drag anywhere, so their positions are **relative to the window title bar**,
|
||||
never fixed screen coordinates. All offsets below are measured from real
|
||||
captures and live at the top of `analyze.py`.
|
||||
|
||||
| Constant | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `TITLE_VERIFY_CROP` | `(14, 12, 150, 42)` | `(left, top, width, height)` crop around a candidate title point |
|
||||
| `TITLE_VERIFY_THRESHOLD` | `115` | binarization threshold for the title crop |
|
||||
| `TITLE_SUBSTRING` | `anufact` / `nven` | OCR substring that proves the title bar |
|
||||
| `MESSAGE_SEARCH` | `(175, 105, 600, 70)` | craft message search region relative to the title |
|
||||
| `MESSAGE_COLOR_GREEN` | `(170, 120, 30)` | green-text pixel filter `(min g, min g-r, min g-b)` |
|
||||
| `MESSAGE_COLOR_RED` | `(170, 100, 60)` | red-text pixel filter `(min r, min r-g, min r-b)` |
|
||||
| `FIND_OCR_SCALES` | `(1.0, 0.75)` | full-frame scan scales; some frames only recover at one |
|
||||
|
||||
- The craft message sits at `(title_x - 161, title_y + 121)` — measured at 5
|
||||
different Manufacturing window positions, all the same offset. `MESSAGE_SEARCH`
|
||||
is a wider region around it that absorbs title-detection jitter.
|
||||
- The title bar is ~11px tall. The verify crop is deliberately generous
|
||||
(`±14/±12` px) because the verify point is the title-bar center, which OCR
|
||||
occasionally reports a few pixels from the glyphs.
|
||||
- Full-frame recovery is approximate: OCR word boxes land within ~15px of the
|
||||
true title. Positions are re-anchored by `verify_window`, so exactness only
|
||||
matters for the message offset, and `MESSAGE_SEARCH` absorbs the jitter.
|
||||
- Reference window positions across the capture sessions: Manufacturing at
|
||||
`(488, 480)`, `(376, 262)`, `(1330, 194)`, `(1005, 565)`, `(281, 676)`,
|
||||
and `(1104, 327)`/`(1104, 337)`; Inventory always `(1629, 686)`.
|
||||
|
||||
## Log format
|
||||
|
||||
One JSON object per line, appended to `analysis.jsonl` (configurable via
|
||||
`--log`):
|
||||
|
||||
```json
|
||||
{"time": "20:31:02", "frame": 4, "is_harvesting": true, "harvest_pixel": "#f2c285", "harvest_luma": 201, "bars": {"mana": 32, "food": 39, "health": 40, "inventory": 106, "action_points": 140}, "manufacturing_window": [1104, 327], "manufacturing_message": {"text": "you successfully created 1 potion of minor healing", "color": "green", "category": "success"}, "inventory_window": [1629, 686]}
|
||||
```
|
||||
|
||||
- `frame` is an integer in live mode, the file name in replay mode.
|
||||
- `bars` values are integers, or `null` when OCR could not read them (e.g.
|
||||
tesseract not installed). Consumers must tolerate `null`.
|
||||
- `manufacturing_window` / `inventory_window` are `[x, y]` title-bar
|
||||
coordinates of the (currently verified) window, or `null` when unverified.
|
||||
- `manufacturing_message` is `{"text", "color", "category"}` or `null`.
|
||||
`text`/`color` are `null` when no message is showing (category `none`);
|
||||
`manufacturing_message` itself is `null` when the Manufacturing window
|
||||
position is unverified. Colors: `green` (success/working), `red`
|
||||
(failed/failed_lost/hungry/overloaded/stopped).
|
||||
- Message categories: `working`, `success`, `stopped`, `failed`,
|
||||
`failed_lost`, `hungry`, `overloaded`, `none`, `unknown`.
|
||||
- This shape is the contract for the future observer — it should not change
|
||||
without updating `tests/` and the observer accordingly.
|
||||
|
||||
## Testing
|
||||
|
||||
`tests/images/` holds the screenshots used as fixtures, grouped by expected
|
||||
behavior:
|
||||
|
||||
```
|
||||
tests/images/harvesting/ is_harvesting must be True
|
||||
tests/images/not_harvesting/ is_harvesting must be False
|
||||
tests/images/corrupt/ must be skipped gracefully (None)
|
||||
tests/images/manufacturing/ craft message at the pinned title position must
|
||||
<category>/*.png classify to the folder's category
|
||||
tests/images/inventory/ verify_window must pass at the pinned position
|
||||
```
|
||||
|
||||
Tests are **folder-driven**: new edge cases are added by dropping a screenshot
|
||||
into the appropriate folder; no test code changes needed unless a new expected
|
||||
*value* must be pinned. `tests/test_bars.py` pins per-frame expectations (e.g.
|
||||
inventory = 110/108/106 depending on frame). `tests/test_windows.py` pins the
|
||||
Manufacturing title position per fixture frame (`MFG_POSITION`), the Inventory
|
||||
position, and a representative subset of `find_windows` recovery cases.
|
||||
|
||||
## Dependencies and setup
|
||||
|
||||
System:
|
||||
|
||||
- `tesseract-ocr` (OCR engine; the Python binding is `pytesseract`)
|
||||
- X11 (for live capture)
|
||||
|
||||
Python (installed in the project virtualenv `.venv/`, see `requirements.txt`):
|
||||
|
||||
- `python-xlib`, `pillow`, `pytesseract`, `pytest`
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
.venv/bin/python -m pytest tests/
|
||||
.venv/bin/python main.py # live capture
|
||||
.venv/bin/python main.py --replay tests/images/harvesting
|
||||
```
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Coordinates are resolution-specific (1920x1080). Resizing the game window
|
||||
requires recalibration of `HARVEST_PIXEL` / `NUMBER_REGIONS`.
|
||||
- OCR depends on the system `tesseract` binary; if missing, bar values log as
|
||||
`null` (harvesting detection is unaffected). Window/message OCR degrades the
|
||||
same way (`verify_window` returns False, messages read as `null`).
|
||||
- The panel appears to be semi-transparent; some scene pixels show through.
|
||||
So far this has not affected detection, but a very different background
|
||||
could.
|
||||
- Full-frame window recovery (`find_windows`) is not 100% reliable — it found
|
||||
the Manufacturing window in all 53 reference frames but missed the Inventory
|
||||
window in one harvesting frame. `WindowTracker` compensates by retrying on
|
||||
every frame while a window stays stale, so a missed recovery only delays the
|
||||
update by a frame or two. The cheap per-frame `verify_window` cache check is
|
||||
the workhorse (53/53) and recovery only runs after a window moves.
|
||||
- The steady-state loop adds roughly 0.5s/frame for the two window verifies
|
||||
plus message OCR; a full-frame recovery scan costs ~5-9s but only happens
|
||||
after a window moves.
|
||||
@@ -15,14 +15,59 @@ import analyze
|
||||
from capture import WindowCapture, WindowNotFound
|
||||
|
||||
|
||||
def analyze_and_print(image, frame, prev, logf):
|
||||
class WindowTracker:
|
||||
"""Cache last-known window positions and re-locate them when they move.
|
||||
|
||||
Cached positions are re-verified cheaply every frame. The first failed
|
||||
verify after a stable period triggers an immediate full-frame rescan;
|
||||
afterwards rescans back off to SCAN_PERIOD so a closed or unfindable
|
||||
window cannot pin the loop to full-frame OCR forever.
|
||||
"""
|
||||
|
||||
SCAN_PERIOD = 30.0 # seconds between recovery rescans while a window is missing
|
||||
|
||||
def __init__(self):
|
||||
self.pos = {"manufacturing": None, "inventory": None}
|
||||
self._last_scan = {"manufacturing": 0.0, "inventory": 0.0}
|
||||
self._verified = {"manufacturing": -1, "inventory": -1}
|
||||
self._frame = 0
|
||||
|
||||
def update(self, image):
|
||||
"""Return currently verified window positions (None when unverified)."""
|
||||
verified = {"manufacturing": None, "inventory": None}
|
||||
for window in ("manufacturing", "inventory"):
|
||||
cached = self.pos[window]
|
||||
if cached is not None and analyze.verify_window(image, cached[0], cached[1], window):
|
||||
verified[window] = cached
|
||||
self._verified[window] = self._frame
|
||||
continue
|
||||
just_verified = cached is not None and self._verified[window] == self._frame - 1
|
||||
if just_verified or (cached is None and self._frame == 0) or (
|
||||
time.monotonic() - self._last_scan[window] >= self.SCAN_PERIOD
|
||||
):
|
||||
self._last_scan[window] = time.monotonic()
|
||||
pos = analyze.find_windows(image)[window]
|
||||
if pos is not None:
|
||||
self.pos[window] = pos
|
||||
verified[window] = pos
|
||||
self._verified[window] = self._frame
|
||||
self._frame += 1
|
||||
return verified
|
||||
|
||||
|
||||
def analyze_and_print(image, frame, prev, logf, tracker):
|
||||
global _last_t
|
||||
info = analyze.summarize(image)
|
||||
harvest = analyze.is_harvesting(image)
|
||||
bars = analyze.read_bar_values(image)
|
||||
windows = tracker.update(image)
|
||||
fps = 1.0 / max(1e-6, time.monotonic() - _last_t)
|
||||
_last_t = time.monotonic()
|
||||
|
||||
mfg = windows["manufacturing"]
|
||||
message = analyze.read_manufacturing_message(image, mfg[0], mfg[1]) if mfg else None
|
||||
inv = windows["inventory"]
|
||||
|
||||
record = {
|
||||
"time": _now(),
|
||||
"frame": frame,
|
||||
@@ -30,6 +75,9 @@ def analyze_and_print(image, frame, prev, logf):
|
||||
"harvest_pixel": harvest["color"],
|
||||
"harvest_luma": harvest["luma"],
|
||||
"bars": bars,
|
||||
"manufacturing_window": [mfg[0], mfg[1]] if mfg else None,
|
||||
"manufacturing_message": message,
|
||||
"inventory_window": [inv[0], inv[1]] if inv else None,
|
||||
}
|
||||
if logf is not None:
|
||||
logf.write(json.dumps(record) + "\n")
|
||||
@@ -41,11 +89,12 @@ def analyze_and_print(image, frame, prev, logf):
|
||||
top = ", ".join(f"{c['hex']} {c['fraction'] * 100:.0f}%" for c in info["dominant"][:3])
|
||||
status = "harvesting" if harvest["is_harvesting"] else "not harvesting"
|
||||
frame_str = f"{frame:05d}" if isinstance(frame, int) else frame
|
||||
msg = f"mfg={mfg} msg={message['category'] if message else None}" if mfg else "mfg=none"
|
||||
print(
|
||||
f"[{frame_str}] {status} luma={harvest['luma']} "
|
||||
f"size={info['size'][0]}x{info['size'][1]} "
|
||||
f"mean={info['mean_rgb']} top=[{top}] bars={bars} "
|
||||
f"delta={delta} loop_fps={fps:.1f}",
|
||||
f"delta={delta} loop_fps={fps:.1f} {msg}",
|
||||
flush=True,
|
||||
)
|
||||
return small
|
||||
@@ -55,6 +104,7 @@ def run_replay(args):
|
||||
"""Process PNGs from a directory instead of a live window."""
|
||||
logf = open(args.log, "a") if args.log else None
|
||||
prev = None
|
||||
tracker = WindowTracker()
|
||||
try:
|
||||
for name in sorted(os.listdir(args.replay)):
|
||||
if not name.lower().endswith(".png"):
|
||||
@@ -64,7 +114,7 @@ def run_replay(args):
|
||||
if image is None:
|
||||
print(f"[skipped] unreadable image: {path}", flush=True)
|
||||
continue
|
||||
prev = analyze_and_print(image, name, prev, logf)
|
||||
prev = analyze_and_print(image, name, prev, logf, tracker)
|
||||
finally:
|
||||
if logf:
|
||||
logf.close()
|
||||
@@ -78,6 +128,7 @@ def run_live(args):
|
||||
print(f"Capturing window matching {args.title!r} at {args.fps:.1f} fps (Ctrl-C to stop)", flush=True)
|
||||
logf = open(args.log, "a") if args.log else None
|
||||
prev = None
|
||||
tracker = WindowTracker()
|
||||
frame = 0
|
||||
try:
|
||||
while True:
|
||||
@@ -88,7 +139,7 @@ def run_live(args):
|
||||
print(f"[{_now()}] {exc}; retrying...", flush=True)
|
||||
time.sleep(1.0)
|
||||
continue
|
||||
prev = analyze_and_print(image, frame, prev, logf)
|
||||
prev = analyze_and_print(image, frame, prev, logf, tracker)
|
||||
|
||||
if args.save_every and frame % args.save_every == 0:
|
||||
path = os.path.join(args.snapshots, f"frame_{frame:05d}.png")
|
||||
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 768 KiB After Width: | Height: | Size: 768 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |