Skip to content

Commit

Permalink
separated cooking.dart
Browse files Browse the repository at this point in the history
  • Loading branch information
liplum committed May 3, 2024
1 parent f29b9da commit 59a344c
Show file tree
Hide file tree
Showing 14 changed files with 594 additions and 565 deletions.
3 changes: 2 additions & 1 deletion escape_wild/lib/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export 'package:escape_wild/core/ecs/ecs.dart';
export 'package:escape_wild/core/level.dart';
export 'package:escape_wild/core/content.dart';
export 'package:escape_wild/core/time.dart';
export 'package:escape_wild/core/item_prop.dart';
export 'package:escape_wild/core/craft.dart';
export 'package:escape_wild/core/i18n.dart';
export 'package:escape_wild/core/hardness.dart';
Expand All @@ -19,7 +20,7 @@ export 'package:escape_wild/core/data_class.dart';
export 'package:escape_wild/core/player.dart';
export 'package:escape_wild/core/route.dart';
export 'package:escape_wild/core/campfire.dart';
export 'package:escape_wild/core/cooking.dart';
export 'package:escape_wild/core/cook/cook.dart';
export 'package:escape_wild/core/item_pool.dart';
export 'package:escape_wild/ambiguous.dart';

Expand Down
156 changes: 155 additions & 1 deletion escape_wild/lib/core/campfire.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'dart:math';

import 'package:json_annotation/json_annotation.dart';

import 'package:escape_wild/core.dart';
import 'package:flutter/widgets.dart';

part 'campfire.g.dart';

@JsonSerializable()
Expand Down Expand Up @@ -36,3 +38,155 @@ class FireState {
fuel: fuel ?? this.fuel,
);
}

abstract class CampfirePlaceProtocol extends PlaceProtocol with ChangeNotifier {
FireState get fireState;

set fireState(FireState v);

List<ItemStack> get onCampfire;

set onCampfire(List<ItemStack> v);

List<ItemStack> get offCampfire;

set offCampfire(List<ItemStack> v);

void onResetCooking();
}

extension CampfireHolderProtocolX on CampfirePlaceProtocol {
bool get isCampfireHasAnyStack => onCampfire.isNotEmpty || offCampfire.isNotEmpty;
}

mixin CampfireCookingMixin on CampfirePlaceProtocol {
@JsonKey(fromJson: tsFromJson, toJson: tsToJson, includeIfNull: false)
Ts cookingTime = Ts.zero;
List<ItemStack> _onCampfire = [];

@override
@JsonKey(fromJson: campfireStackFromJson, toJson: campfireStackToJson, includeIfNull: false)
List<ItemStack> get onCampfire => _onCampfire;

@override
set onCampfire(List<ItemStack> v) {
_onCampfire = v;
notifyListeners();
}

List<ItemStack> _offCampfire = [];

@override
@JsonKey(fromJson: campfireStackFromJson, toJson: campfireStackToJson, includeIfNull: false)
List<ItemStack> get offCampfire => _offCampfire;

@override
set offCampfire(List<ItemStack> v) {
_offCampfire = v;
notifyListeners();
}

@CookRecipeProtocol.jsonKey
CookRecipeProtocol? recipe;

FireState _fireState = FireState.off;

@override
@JsonKey(fromJson: fireStateFromJson, toJson: fireStateStackToJson, includeIfNull: false)
FireState get fireState => _fireState;

@override
set fireState(FireState v) {
_fireState = v;
notifyListeners();
}

double get fuelCostPerMinute;

/// Call this after changing [onCampfire].
@override
void onResetCooking() {
cookingTime = Ts.zero;
final matched = matchCookRecipe(onCampfire);
recipe = matched;
if (matched != null) {
// for instant cooking
final changed = matched.onMatch(onCampfire, offCampfire);
if (changed) {
notifyListeners();
}
}
}

Future<void> onCampfirePass(Ts delta) async {
// update items the place holds
for (final stack in onCampfire) {
await stack.onPassTime(delta);
}
for (final stack in offCampfire) {
await stack.onPassTime(delta);
}
if (fireState.active) {
final cost = delta.minutes * fuelCostPerMinute;
fireState = _burningFuel(fireState, cost);
}
// only cooking when fire has fuel.
if (fireState.fuel <= 0) return;
if (onCampfire.isEmpty) return;
final recipe = this.recipe ??= matchCookRecipe(onCampfire);
if (recipe == null) {
cookingTime = Ts.zero;
} else {
cookingTime += delta;
final changed = recipe.updateCooking(onCampfire, offCampfire, cookingTime, delta);
if (changed) {
this.recipe = null;
cookingTime = Ts.zero;
notifyListeners();
}
}
}

Future<void> onFirePass(double fuelCostSpeed, Ts delta) async {
final fireState = this.fireState;
if (fireState.active) {
final cost = delta / actionStepTime * fuelCostSpeed;
this.fireState = _burningFuel(fireState, cost);
}
}

static List<ItemStack> campfireStackFromJson(dynamic json) =>
json == null ? [] : (json as List<dynamic>).map((e) => ItemStack.fromJson(e as Map<String, dynamic>)).toList();

static dynamic campfireStackToJson(List<ItemStack> list) => list.isEmpty ? null : list;

static Ts tsFromJson(dynamic json) => json == null ? Ts.zero : Ts.fromJson((json as num).toInt());

static dynamic tsToJson(Ts ts) => ts == Ts.zero ? null : ts;

static FireState fireStateFromJson(dynamic json) => json == null ? FireState.off : FireState.fromJson(json);

static dynamic fireStateStackToJson(FireState fire) => fire.isOff ? null : fire;
}

const _emberCostFactor = 5;

// TODO: Better formula
FireState _burningFuel(
FireState former,
double cost,
) {
final curFuel = former.fuel;
var resFuel = curFuel;
var resEmber = former.ember;
if (curFuel <= cost) {
final costOverflow = cost - curFuel;
resFuel = 0;
resEmber += curFuel;
resEmber -= costOverflow * _emberCostFactor;
} else {
resFuel -= cost;
resEmber += cost;
}
return FireState(ember: resEmber, fuel: resFuel);
}
33 changes: 33 additions & 0 deletions escape_wild/lib/core/cook/container.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:escape_wild/core.dart';
import 'package:jconverter/jconverter.dart';

/// [ContainerCookRecipe] will transform a certain [Item] that meets [inputTags] into [result] by a certain ratio.
/// It only allow one input.
///
/// For example,
class ContainerCookRecipe extends CookRecipeProtocol implements JConvertibleProtocol {
final List<String> inputTags;
final ItemGetter result;

ContainerCookRecipe(super.name, this.inputTags, this.result);

@override
bool match(List<ItemStack> inputs) {
throw UnimplementedError();
}

@override
bool updateCooking(
List<ItemStack> inputs,
List<ItemStack> outputs,
Ts totalTimePassed,
Ts delta,
) {
throw UnimplementedError();
}

static const type = "ContainerCookRecipe";

@override
String get typeName => type;
}
87 changes: 87 additions & 0 deletions escape_wild/lib/core/cook/continuous.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'dart:math';

import 'package:escape_wild/core.dart';
import 'package:jconverter/jconverter.dart';
import 'package:json_annotation/json_annotation.dart';

part 'continuous.g.dart';

/// [ContinuousCookRecipe] will continuously convert a certain [Item] that meets [ingredient] into [dish] by a certain ratio.
/// - [ingredient] only matches one whose [Item.mergeable] is true.
///
/// It doesn't allow [Item.container].
@JsonSerializable(createToJson: false)
class ContinuousCookRecipe extends CookRecipeProtocol implements JConvertibleProtocol {
@JsonKey()
final Iterable<String> ingredient;
@itemGetterJsonKey
final ItemGetter dish;

/// [speed] is how much [ingredient] that will be transform to [dish] per minutes.
/// ```dart
/// int mass;
/// int minute;
/// speed = mass / minute;
/// ```
/// Unit: g/min
final double speed;

/// [ratio] works with [speed]. It's how much [dish] based on [speed]
/// ```dart
/// int transformedInput = speed * time;
/// int outputMass = transformedInput * ratio;
/// ```
///
/// The default is 1.0
final double ratio;

ContinuousCookRecipe(
super.name, {
required this.ingredient,
required this.dish,
required this.speed,
this.ratio = 1.0,
}) {
assert(ingredient.isNotEmpty, "Input tags of $registerName is empty.");
}

@override
bool match(List<ItemStack> inputs) {
if (inputs.length != 1) return false;
final input = inputs.first;
if (!input.meta.mergeable) return false;
if (!input.meta.hasTags(ingredient)) return false;
return true;
}

@override
bool updateCooking(
List<ItemStack> inputs,
List<ItemStack> outputs,
Ts totalTimePassed,
Ts delta,
) {
if (inputs.length != 1) return false;
final input = inputs.first;
if (!input.meta.mergeable) return false;
if (!input.meta.hasTags(ingredient)) return false;
double transformedInputF = min(speed * delta.minutes, input.stackMass.toDouble());
double outputMassF = transformedInputF * ratio;
// to avoid lose of precision.
int transformedInput = transformedInputF.toInt();
int outputMass = outputMassF.toInt();
input.mass = input.stackMass - transformedInput;
inputs.cleanEmptyStack();
final result = dish();
final resultStack = result.create(mass: outputMass);
outputs.addItemOrMerge(resultStack);
return true;
}

factory ContinuousCookRecipe.fromJson(Map<String, dynamic> json) => _$ContinuousCookRecipeFromJson(json);

static const type = "TransformCookRecipe";

@override
String get typeName => type;
}
15 changes: 15 additions & 0 deletions escape_wild/lib/core/cook/continuous.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions escape_wild/lib/core/cook/cook.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export "container.dart";
export "continuous.dart";
export "instant.dart";
export "protocol.dart";
export "timed.dart";
Loading

0 comments on commit 59a344c

Please sign in to comment.