Compare commits
4 Commits
15a2f37a3d
...
91d0f50fd4
Author | SHA1 | Date | |
---|---|---|---|
91d0f50fd4 | |||
385fe651d4 | |||
9f0db64482 | |||
3e6aea5668 |
20
.gitea/workflows/publish.yaml
Normal file
20
.gitea/workflows/publish.yaml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
name: Podman Image CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Podman
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y podman
|
||||||
|
- name: Build the image
|
||||||
|
run: podman build --tag git.jeffa.io/jeff/trade_bot .
|
||||||
|
- name: Push the image
|
||||||
|
run: podman push git.jeffa.io/jeff/trade_bot
|
@ -6,6 +6,7 @@ orientation = 0
|
|||||||
"iron|sword.greatsword|sword.long" = 1_000
|
"iron|sword.greatsword|sword.long" = 1_000
|
||||||
|
|
||||||
[sell_prices]
|
[sell_prices]
|
||||||
|
"food.cheese" = 1_000_000
|
||||||
"consumable.potion_minor" = 150
|
"consumable.potion_minor" = 150
|
||||||
|
|
||||||
# Modular Weapons
|
# Modular Weapons
|
||||||
|
109
src/bot.rs
109
src/bot.rs
@ -33,18 +33,13 @@ use crate::config::PriceList;
|
|||||||
|
|
||||||
const COINS: ItemDefinitionId =
|
const COINS: ItemDefinitionId =
|
||||||
ItemDefinitionId::Simple(Cow::Borrowed("common.items.utility.coins"));
|
ItemDefinitionId::Simple(Cow::Borrowed("common.items.utility.coins"));
|
||||||
|
const CLIENT_TPS: Duration = Duration::from_millis(33);
|
||||||
/// TODO: Implement Display
|
const TRADE_ACTION_DELAY: Duration = Duration::from_millis(300);
|
||||||
#[derive(Debug)]
|
const ACCOUNCEMENT_DELAY: Duration = Duration::from_mins(45);
|
||||||
pub struct Reciept {
|
const OUCH_DELAY: Duration = Duration::from_secs(2);
|
||||||
pub my_items: HashMap<String, u32>,
|
|
||||||
pub their_items: HashMap<String, u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An active connection to the Veloren server that will attempt to run every time the `tick`
|
/// An active connection to the Veloren server that will attempt to run every time the `tick`
|
||||||
/// function is called.
|
/// function is called.
|
||||||
///
|
|
||||||
/// See the [module-level documentation](index.html) for more information.
|
|
||||||
pub struct Bot {
|
pub struct Bot {
|
||||||
username: String,
|
username: String,
|
||||||
position: Pos,
|
position: Pos,
|
||||||
@ -91,7 +86,7 @@ impl Bot {
|
|||||||
log::info!("Connecting to veloren");
|
log::info!("Connecting to veloren");
|
||||||
|
|
||||||
let mut client = connect_to_veloren(game_server, auth_server, &username, password)?;
|
let mut client = connect_to_veloren(game_server, auth_server, &username, password)?;
|
||||||
let mut clock = Clock::new(Duration::from_secs_f64(1.0 / 30.0));
|
let mut clock = Clock::new(CLIENT_TPS);
|
||||||
|
|
||||||
client.load_character_list();
|
client.load_character_list();
|
||||||
|
|
||||||
@ -134,10 +129,7 @@ impl Bot {
|
|||||||
let position = if let Some(coords) = position {
|
let position = if let Some(coords) = position {
|
||||||
Pos(coords.into())
|
Pos(coords.into())
|
||||||
} else {
|
} else {
|
||||||
client
|
client.position().map(Pos).ok_or("Failed to get position")?
|
||||||
.position()
|
|
||||||
.map(|coords| Pos(coords))
|
|
||||||
.ok_or("Failed to get position")?
|
|
||||||
};
|
};
|
||||||
let orientation = if let Some(orientation) = orientation {
|
let orientation = if let Some(orientation) = orientation {
|
||||||
Ori::new(Quaternion::rotation_z(orientation.to_radians()))
|
Ori::new(Quaternion::rotation_z(orientation.to_radians()))
|
||||||
@ -192,7 +184,7 @@ impl Bot {
|
|||||||
self.handle_veloren_event(event)?;
|
self.handle_veloren_event(event)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.last_trade_action.elapsed() > Duration::from_millis(300) {
|
if self.last_trade_action.elapsed() > TRADE_ACTION_DELAY {
|
||||||
self.client.respawn();
|
self.client.respawn();
|
||||||
self.handle_position_and_orientation()?;
|
self.handle_position_and_orientation()?;
|
||||||
self.handle_lantern();
|
self.handle_lantern();
|
||||||
@ -232,7 +224,7 @@ impl Bot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.last_announcement.elapsed() > Duration::from_mins(45) {
|
if self.last_announcement.elapsed() > ACCOUNCEMENT_DELAY {
|
||||||
self.handle_announcement()?;
|
self.handle_announcement()?;
|
||||||
|
|
||||||
self.last_announcement = Instant::now();
|
self.last_announcement = Instant::now();
|
||||||
@ -305,7 +297,7 @@ impl Bot {
|
|||||||
}
|
}
|
||||||
"price" => {
|
"price" => {
|
||||||
for item_name in split_content {
|
for item_name in split_content {
|
||||||
self.send_price_info(&sender, &item_name)?;
|
self.send_price_info(&sender, item_name)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -372,7 +364,7 @@ impl Bot {
|
|||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if let Some(uid) = self.client.uid() {
|
if let Some(uid) = self.client.uid() {
|
||||||
if uid == target && self.last_ouch.elapsed() > Duration::from_secs(2) {
|
if uid == target && self.last_ouch.elapsed() > OUCH_DELAY {
|
||||||
self.client
|
self.client
|
||||||
.send_command("say".to_string(), vec!["Ouch!".to_string()]);
|
.send_command("say".to_string(), vec!["Ouch!".to_string()]);
|
||||||
|
|
||||||
@ -388,7 +380,7 @@ impl Bot {
|
|||||||
if let Some(uid) = self.client.uid() {
|
if let Some(uid) = self.client.uid() {
|
||||||
if uid == info.target
|
if uid == info.target
|
||||||
&& info.amount.is_sign_negative()
|
&& info.amount.is_sign_negative()
|
||||||
&& self.last_ouch.elapsed() > Duration::from_secs(1)
|
&& self.last_ouch.elapsed() > OUCH_DELAY
|
||||||
{
|
{
|
||||||
self.client
|
self.client
|
||||||
.send_command("say".to_string(), vec!["That hurt!".to_string()]);
|
.send_command("say".to_string(), vec!["That hurt!".to_string()]);
|
||||||
@ -519,6 +511,8 @@ impl Bot {
|
|||||||
/// 6. If the total value of my offer is greater than the total value of their offer:
|
/// 6. If the total value of my offer is greater than the total value of their offer:
|
||||||
/// 1. If I am offering coins, remove them to balance.
|
/// 1. If I am offering coins, remove them to balance.
|
||||||
/// 2. If I am not offering coins, add theirs to balance.
|
/// 2. If I am not offering coins, add theirs to balance.
|
||||||
|
/// 7. If the trade is still unbalanced, tell them the value of the greater offer and the
|
||||||
|
/// other party's total coin amount.
|
||||||
///
|
///
|
||||||
/// See the inline comments for more details.
|
/// See the inline comments for more details.
|
||||||
#[allow(clippy::comparison_chain)]
|
#[allow(clippy::comparison_chain)]
|
||||||
@ -527,6 +521,7 @@ impl Bot {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let phase = trade.phase;
|
||||||
let my_offer_index = trade
|
let my_offer_index = trade
|
||||||
.which_party(self.client.uid().ok_or("Failed to get uid")?)
|
.which_party(self.client.uid().ok_or("Failed to get uid")?)
|
||||||
.ok_or("Failed to get offer index")?;
|
.ok_or("Failed to get offer index")?;
|
||||||
@ -537,6 +532,11 @@ impl Bot {
|
|||||||
&trade.offers[their_offer_index],
|
&trade.offers[their_offer_index],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let their_uid = trade.parties[their_offer_index];
|
||||||
|
let their_name = self
|
||||||
|
.find_player_alias(&their_uid)
|
||||||
|
.ok_or("Failed to find player name")?
|
||||||
|
.clone();
|
||||||
|
|
||||||
let inventories = self.client.inventories();
|
let inventories = self.client.inventories();
|
||||||
let me = self.client.entity();
|
let me = self.client.entity();
|
||||||
@ -554,6 +554,16 @@ impl Bot {
|
|||||||
let coins_owned = COINS.to_owned();
|
let coins_owned = COINS.to_owned();
|
||||||
let get_my_coins = my_inventory.get_slot_of_item_by_def_id(&coins_owned);
|
let get_my_coins = my_inventory.get_slot_of_item_by_def_id(&coins_owned);
|
||||||
let get_their_coins = their_inventory.get_slot_of_item_by_def_id(&coins_owned);
|
let get_their_coins = their_inventory.get_slot_of_item_by_def_id(&coins_owned);
|
||||||
|
let my_coin_amount = if let Some(coins) = get_my_coins {
|
||||||
|
my_inventory.get(coins).unwrap().amount()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let their_coin_amount = if let Some(coins) = get_their_coins {
|
||||||
|
their_inventory.get(coins).unwrap().amount()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
let mut receipt = Reciept {
|
let mut receipt = Reciept {
|
||||||
my_items: HashMap::new(),
|
my_items: HashMap::new(),
|
||||||
their_items: HashMap::new(),
|
their_items: HashMap::new(),
|
||||||
@ -659,8 +669,6 @@ impl Bot {
|
|||||||
|
|
||||||
drop(inventories);
|
drop(inventories);
|
||||||
|
|
||||||
let phase = trade.phase;
|
|
||||||
|
|
||||||
// If the trade hasn't changed, do nothing to avoid spamming the server.
|
// If the trade hasn't changed, do nothing to avoid spamming the server.
|
||||||
if let Some(previous) = &self.previous_trade {
|
if let Some(previous) = &self.previous_trade {
|
||||||
if previous == &trade {
|
if previous == &trade {
|
||||||
@ -672,6 +680,8 @@ impl Bot {
|
|||||||
// offer now. The trade action is infallible from here.
|
// offer now. The trade action is infallible from here.
|
||||||
self.previous_trade = Some(trade);
|
self.previous_trade = Some(trade);
|
||||||
|
|
||||||
|
log::info!("Performing trade action with {their_name}");
|
||||||
|
|
||||||
// Before running any actual trade logic, remove items that are not for sale or not being
|
// Before running any actual trade logic, remove items that are not for sale or not being
|
||||||
// purchased. End this trade action if an item was removed.
|
// purchased. End this trade action if an item was removed.
|
||||||
|
|
||||||
@ -699,8 +709,7 @@ impl Bot {
|
|||||||
|
|
||||||
// The if/else statements below implement the bot's main feature: buying, selling and
|
// The if/else statements below implement the bot's main feature: buying, selling and
|
||||||
// trading items according to the values set in the configuration file. Coins are used to
|
// trading items according to the values set in the configuration file. Coins are used to
|
||||||
// balance the value of the trade. In the case that we try to add more coins than are
|
// balance the value of the trade.
|
||||||
// available, the server will correct it by adding all of the available coins.
|
|
||||||
|
|
||||||
// If the trade is balanced
|
// If the trade is balanced
|
||||||
if difference == 0 {
|
if difference == 0 {
|
||||||
@ -708,50 +717,94 @@ impl Bot {
|
|||||||
|
|
||||||
// Accept
|
// Accept
|
||||||
self.client.perform_trade_action(TradeAction::Accept(phase));
|
self.client.perform_trade_action(TradeAction::Accept(phase));
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// If they are offering more
|
// If they are offering more
|
||||||
} else if difference > 0 {
|
if difference > 0 {
|
||||||
// If they are offering coins
|
// If they are offering coins
|
||||||
if their_offered_coin_amount > 0 {
|
if their_offered_coin_amount > 0 {
|
||||||
if let Some(their_coins) = get_their_coins {
|
if let Some(their_coins) = get_their_coins {
|
||||||
|
if their_coin_amount >= difference as u32 {
|
||||||
// Remove their coins to balance
|
// Remove their coins to balance
|
||||||
self.client.perform_trade_action(TradeAction::RemoveItem {
|
self.client.perform_trade_action(TradeAction::RemoveItem {
|
||||||
item: their_coins,
|
item: their_coins,
|
||||||
quantity: difference as u32,
|
quantity: difference as u32,
|
||||||
ours: false,
|
ours: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If they are not offering coins
|
// If they are not offering coins
|
||||||
} else if let Some(my_coins) = get_my_coins {
|
} else if let Some(my_coins) = get_my_coins {
|
||||||
|
if my_coin_amount >= difference as u32 {
|
||||||
// Add my coins to balanace
|
// Add my coins to balanace
|
||||||
self.client.perform_trade_action(TradeAction::AddItem {
|
self.client.perform_trade_action(TradeAction::AddItem {
|
||||||
item: my_coins,
|
item: my_coins,
|
||||||
quantity: difference as u32,
|
quantity: difference as u32,
|
||||||
ours: true,
|
ours: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_command(
|
||||||
|
"tell".to_string(),
|
||||||
|
vec![
|
||||||
|
their_name,
|
||||||
|
format!(
|
||||||
|
"The value of your offer is {their_offered_items_value} coins. I only have {my_coin_amount} coins.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If I am offering more
|
// If I am offering more
|
||||||
} else if difference < 0 {
|
if difference < 0 {
|
||||||
// If I am offering coins
|
// If I am offering coins
|
||||||
if my_offered_coin_amount > 0 {
|
if my_offered_coin_amount > 0 {
|
||||||
if let Some(my_coins) = get_my_coins {
|
if let Some(my_coins) = get_my_coins {
|
||||||
|
if my_coin_amount >= difference.unsigned_abs() {
|
||||||
// Remove my coins to balance
|
// Remove my coins to balance
|
||||||
self.client.perform_trade_action(TradeAction::RemoveItem {
|
self.client.perform_trade_action(TradeAction::RemoveItem {
|
||||||
item: my_coins,
|
item: my_coins,
|
||||||
quantity: difference.unsigned_abs(),
|
quantity: difference.unsigned_abs(),
|
||||||
ours: true,
|
ours: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If I am not offering coins
|
// If I am not offering coins
|
||||||
} else if let Some(their_coins) = get_their_coins {
|
} else if let Some(their_coins) = get_their_coins {
|
||||||
|
if their_coin_amount >= difference.unsigned_abs() {
|
||||||
// Add their coins to balance
|
// Add their coins to balance
|
||||||
self.client.perform_trade_action(TradeAction::AddItem {
|
self.client.perform_trade_action(TradeAction::AddItem {
|
||||||
item: their_coins,
|
item: their_coins,
|
||||||
quantity: difference.unsigned_abs(),
|
quantity: difference.unsigned_abs(),
|
||||||
ours: false,
|
ours: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let their_name = self.find_player_alias(&their_uid).unwrap().clone();
|
||||||
|
|
||||||
|
self.client.send_command(
|
||||||
|
"tell".to_string(),
|
||||||
|
vec![
|
||||||
|
their_name,
|
||||||
|
format!(
|
||||||
|
"The value of my offer is {my_offered_items_value} coins. You only have {their_coin_amount} coins.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -950,6 +1003,12 @@ impl Bot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Reciept {
|
||||||
|
pub my_items: HashMap<String, u32>,
|
||||||
|
pub their_items: HashMap<String, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum TradeMode {
|
enum TradeMode {
|
||||||
AdminAccess,
|
AdminAccess,
|
||||||
|
Loading…
Reference in New Issue
Block a user