class_name LandGridView extends RefCounted var _owner: Control var _game_view: Control var _game_config var _crop_phase_rules var _get_land_node: Callable var _render_add_land_indicator: Callable var _land_count := 12 var _land_texture_size := Vector2(172.0, 102.0) var _textures: Dictionary = {} var _plant_layer: Control var _plant_state_layer: Control func setup(options: Dictionary) -> void: _owner = options.get("owner") as Control _game_view = options.get("game_view") as Control _game_config = options.get("game_config") _crop_phase_rules = options.get("crop_phase_rules") _get_land_node = options.get("get_land_node", Callable()) _render_add_land_indicator = options.get("render_add_land_indicator", Callable()) _land_count = int(options.get("land_count", _land_count)) _land_texture_size = options.get("land_texture_size", _land_texture_size) _textures = options.get("textures", {}) _create_layers() func plant_layer() -> Control: return _plant_layer func plant_state_layer() -> Control: return _plant_state_layer func render(lands_by_position: Dictionary) -> void: _clear_children(_plant_layer) _clear_children(_plant_state_layer) for position in range(_land_count): var land_node := _get_land_node_for(position) if land_node == null: continue var land_data: Dictionary = lands_by_position.get(position, {}) land_node.texture = _get_land_texture(land_data) if not land_data.is_empty() and int(land_data.get("crop_id", 0)) > 0: _render_plant_on_land(position, land_data) _render_disease_state(position, land_data) if _render_add_land_indicator.is_valid(): _render_add_land_indicator.call() func build_signature(lands_by_position: Dictionary) -> String: var parts := PackedStringArray() for position in range(_land_count): var land_data: Dictionary = lands_by_position.get(position, {}) if land_data.is_empty(): parts.append("%s:locked" % position) continue var crop_id := int(land_data.get("crop_id", 0)) if crop_id <= 0: parts.append("%s:empty:%s" % [position, _land_level(land_data)]) continue parts.append("%s:%s:%s:%s:%s:%s:%s:%s:%s:%s" % [ position, crop_id, _get_plant_phase(land_data), int(land_data.get("crop_num", 0)), int(land_data.get("has_gather", 0)), int(land_data.get("has_bug", 0)), int(land_data.get("has_grass", 0)), int(land_data.get("has_dry", 0)), int(land_data.get("crop_min_num", 0)), ",".join(_friend_interaction_types(land_data)), ]) return "|".join(parts) func get_plant_phase(land_data: Dictionary) -> int: return _get_plant_phase(land_data) func get_crop_phase_times(crop_id: int) -> Array[int]: if _crop_phase_rules == null: return [1, 1, 1] return _crop_phase_rules.get_crop_phase_times(crop_id) func _create_layers() -> void: if _game_view == null: return _plant_layer = Control.new() _plant_layer.name = "PlantLayer" _plant_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE _plant_layer.set_anchors_preset(Control.PRESET_FULL_RECT) _game_view.add_child(_plant_layer) _plant_state_layer = Control.new() _plant_state_layer.name = "PlantStateLayer" _plant_state_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE _plant_state_layer.set_anchors_preset(Control.PRESET_FULL_RECT) _game_view.add_child(_plant_state_layer) func _get_land_node_for(position: int) -> TextureRect: if not _get_land_node.is_valid(): return null return _get_land_node.call(position) as TextureRect func _get_land_texture(land_data: Dictionary) -> Texture2D: if land_data.is_empty(): return _textures.get("locked") match _land_level(land_data): 1: return _textures.get("level_1") 2: return _textures.get("level_2") 3: return _textures.get("level_3") _: return _textures.get("level_0") func _render_plant_on_land(position: int, land_data: Dictionary) -> void: var land_node := _get_land_node_for(position) if land_node == null or _plant_layer == null: return var phase := _get_plant_phase(land_data) var texture := _get_plant_texture(int(land_data.get("crop_id", 0)), phase) if texture == null: return var plant := TextureRect.new() plant.name = "Plant%s" % position plant.texture = texture plant.size = texture.get_size() plant.mouse_filter = Control.MOUSE_FILTER_IGNORE plant.expand_mode = TextureRect.EXPAND_IGNORE_SIZE plant.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED var land_center := land_node.position + land_node.size * 0.5 var offset_y := -plant.size.y if phase == 0 else -plant.size.y + 10.0 plant.position = land_center + Vector2(-plant.size.x * 0.5 - 10.0, offset_y) _plant_layer.add_child(plant) func _render_disease_state(position: int, land_data: Dictionary) -> void: if _plant_state_layer == null: return var phase := _get_plant_phase(land_data) if phase < 0 or phase > 2: return var disease_textures: Array[Texture2D] = [] if int(land_data.get("has_grass", 0)) == 1: disease_textures.append(_textures.get("state_grass")) if int(land_data.get("has_bug", 0)) == 1: disease_textures.append(_textures.get("state_bug")) if int(land_data.get("has_dry", 0)) == 1: disease_textures.append(_textures.get("state_dry")) if disease_textures.is_empty(): return var land_node := _get_land_node_for(position) if land_node == null: return var state_group := Control.new() state_group.name = "PlantState%s" % position state_group.mouse_filter = Control.MOUSE_FILTER_IGNORE state_group.size = Vector2(33.0 * disease_textures.size(), 40.0) var land_center := land_node.position + land_node.size * 0.5 state_group.position = Vector2( land_center.x - state_group.size.x * 0.5 - 13.0, land_center.y + (-100.0 if phase == 2 else -70.0) ) for index in range(disease_textures.size()): var texture := disease_textures[index] if texture == null: continue var icon := TextureRect.new() icon.name = "StateIcon%s" % index icon.texture = texture icon.position = Vector2(33.0 * index, -10.0 if disease_textures.size() == 3 and index == 1 else 0.0) icon.size = texture.get_size() icon.mouse_filter = Control.MOUSE_FILTER_IGNORE state_group.add_child(icon) _plant_state_layer.add_child(state_group) if _owner == null: return var tween := _owner.create_tween().bind_node(state_group).set_loops() tween.tween_property(state_group, "position:y", state_group.position.y - 4.0, 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(state_group, "position:y", state_group.position.y, 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) func _friend_interaction_types(land_data: Dictionary) -> PackedStringArray: var result := PackedStringArray() var raw_types = land_data.get("interaction_type_list", []) if not raw_types is Array: return result for raw_type in raw_types: result.append(str(int(raw_type))) return result func _get_plant_phase(land_data: Dictionary) -> int: if _crop_phase_rules == null: return int(land_data.get("crop_phase", -1)) return _crop_phase_rules.get_phase(land_data) func _get_plant_texture(crop_id: int, phase: int) -> Texture2D: var path := "res://assets/egret/plant/plant_p0.png" if phase == 4: path = "res://assets/egret/plant/plant_p4.png" elif phase > 0 and _game_config != null: var plant_graphic_id := int(_game_config.get_crop_graphic_id(crop_id, phase)) path = "res://assets/egret/plant/%s.png" % plant_graphic_id if not ResourceLoader.exists(path): path = "res://assets/egret/plant/plant_p0.png" return load(path) func _land_level(land_data: Dictionary) -> int: return clampi(int(land_data.get("level", land_data.get("landLv", 0))), 0, 3) func _clear_children(parent: Node) -> void: if parent == null: return for child in parent.get_children(): parent.remove_child(child) child.queue_free()